mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40:06 -04:00
wgpu: Allow configuring VSync and frame latency at runtime (#8114)
Let apps change present_mode and desired_maximum_frame_latency at
runtime instead of only at startup.
API changes (egui-wgpu):
- New SurfaceConfig { present_mode, desired_maximum_frame_latency }.
- WgpuConfiguration now nests these as pub surface: SurfaceConfig (was
two top-level fields).
- RenderState gains pub surface_config: SurfaceConfig — the
currently-requested value.
API additions (eframe):
- Frame::wgpu_surface_config() / Frame::set_wgpu_surface_config(...) for
get/set.
- SurfaceConfig re-exported as eframe::SurfaceConfig.
How it works:
The wgpu painter compares render_state.surface_config to its
currently-applied values each paint. If they differ it updates its
config and flips
needs_reconfigure on every surface, piggybacking on the existing
deferred-reconfigure pathway.
Demo:
The backend panel (egui_demo_app) gets dropdowns for present mode and
desired max frame latency, wired through the new Frame accessors.
<img width="282" height="172" alt="image"
src="https://github.com/user-attachments/assets/0b1274b2-7e4e-4413-969b-0a014c415f79"
/>
This commit is contained in:
@@ -469,7 +469,8 @@ impl Default for NativeOptions {
|
|||||||
centered: false,
|
centered: false,
|
||||||
|
|
||||||
#[cfg(feature = "wgpu_no_default_features")]
|
#[cfg(feature = "wgpu_no_default_features")]
|
||||||
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
|
wgpu_options: egui_wgpu::WgpuConfiguration::default()
|
||||||
|
.with_surface_config(egui_wgpu::SurfaceConfig::LOW_LATENCY),
|
||||||
|
|
||||||
persist_window: true,
|
persist_window: true,
|
||||||
|
|
||||||
@@ -793,6 +794,28 @@ impl Frame {
|
|||||||
pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> {
|
pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> {
|
||||||
self.wgpu_render_state.as_ref()
|
self.wgpu_render_state.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The currently-applied runtime surface config (present mode, frame latency)
|
||||||
|
/// used by the `wgpu` renderer, if any.
|
||||||
|
///
|
||||||
|
/// Returns `None` when not using the `wgpu` backend.
|
||||||
|
#[cfg(feature = "wgpu_no_default_features")]
|
||||||
|
pub fn wgpu_surface_config(&self) -> Option<egui_wgpu::SurfaceConfig> {
|
||||||
|
self.wgpu_render_state
|
||||||
|
.as_ref()
|
||||||
|
.map(|state| state.surface_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the runtime surface config (present mode, frame latency) for the `wgpu`
|
||||||
|
/// renderer. The surface is reconfigured on the next paint.
|
||||||
|
///
|
||||||
|
/// No-op when not using the `wgpu` backend.
|
||||||
|
#[cfg(feature = "wgpu_no_default_features")]
|
||||||
|
pub fn set_wgpu_surface_config(&mut self, config: egui_wgpu::SurfaceConfig) {
|
||||||
|
if let Some(state) = &mut self.wgpu_render_state {
|
||||||
|
state.surface_config = config;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about the web environment (if applicable).
|
/// Information about the web environment (if applicable).
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ pub use {egui, egui::emath, egui::epaint};
|
|||||||
pub use {egui_glow, glow};
|
pub use {egui_glow, glow};
|
||||||
|
|
||||||
#[cfg(feature = "wgpu_no_default_features")]
|
#[cfg(feature = "wgpu_no_default_features")]
|
||||||
pub use {egui_wgpu, egui_wgpu::wgpu};
|
pub use {egui_wgpu, egui_wgpu::SurfaceConfig, egui_wgpu::WgpuConfiguration, egui_wgpu::wgpu};
|
||||||
|
|
||||||
mod epi;
|
mod epi;
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ impl WebPainterWgpu {
|
|||||||
|
|
||||||
let surface_configuration = wgpu::SurfaceConfiguration {
|
let surface_configuration = wgpu::SurfaceConfiguration {
|
||||||
format: render_state.target_format,
|
format: render_state.target_format,
|
||||||
present_mode: wgpu_options.present_mode,
|
present_mode: wgpu_options.surface.present_mode,
|
||||||
view_formats: vec![render_state.target_format],
|
view_formats: vec![render_state.target_format],
|
||||||
..default_configuration
|
..default_configuration
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,6 +64,43 @@ pub enum WgpuError {
|
|||||||
HandleError(#[from] ::winit::raw_window_handle::HandleError),
|
HandleError(#[from] ::winit::raw_window_handle::HandleError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runtime-mutable subset of [`WgpuConfiguration`].
|
||||||
|
///
|
||||||
|
/// Edit any field to have the surface reconfigured on the next paint.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SurfaceConfig {
|
||||||
|
/// Present mode used for the primary surface.
|
||||||
|
pub present_mode: wgpu::PresentMode,
|
||||||
|
|
||||||
|
/// Desired maximum number of frames that the presentation engine should queue in advance.
|
||||||
|
///
|
||||||
|
/// Use `1` for low-latency, and `2` for high-throughput.
|
||||||
|
///
|
||||||
|
/// See [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`] for details.
|
||||||
|
///
|
||||||
|
/// `None` => Let `wgpu` pick a default (currently `2`).
|
||||||
|
pub desired_maximum_frame_latency: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SurfaceConfig {
|
||||||
|
/// Good default for GUIs with very little (or no) extra GPU work.
|
||||||
|
pub const LOW_LATENCY: Self = Self {
|
||||||
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
|
desired_maximum_frame_latency: if cfg!(target_os = "ios") {
|
||||||
|
None // The default is good on iOS, while `Some(1)` cuts FPS in half
|
||||||
|
} else {
|
||||||
|
Some(1) // Low-latency by default.
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Good default for GUIs with a lot of extra GPU work,
|
||||||
|
/// or that want to prioritize smoothness over latency.
|
||||||
|
pub const HIGH_THROUGHPUT: Self = Self {
|
||||||
|
present_mode: wgpu::PresentMode::AutoVsync,
|
||||||
|
desired_maximum_frame_latency: Some(2), // High-throughput.
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Access to the render state for egui.
|
/// Access to the render state for egui.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RenderState {
|
pub struct RenderState {
|
||||||
@@ -88,6 +125,11 @@ pub struct RenderState {
|
|||||||
|
|
||||||
/// Egui renderer responsible for drawing the UI.
|
/// Egui renderer responsible for drawing the UI.
|
||||||
pub renderer: Arc<RwLock<Renderer>>,
|
pub renderer: Arc<RwLock<Renderer>>,
|
||||||
|
|
||||||
|
/// Runtime-mutable subset of the wgpu configuration.
|
||||||
|
///
|
||||||
|
/// Update this to have the surface reconfigured on the next paint.
|
||||||
|
pub surface_config: SurfaceConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn request_adapter(
|
async fn request_adapter(
|
||||||
@@ -243,6 +285,7 @@ impl RenderState {
|
|||||||
queue,
|
queue,
|
||||||
target_format,
|
target_format,
|
||||||
renderer: Arc::new(RwLock::new(renderer)),
|
renderer: Arc::new(RwLock::new(renderer)),
|
||||||
|
surface_config: config.surface,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,17 +316,11 @@ pub enum SurfaceErrorAction {
|
|||||||
/// Configuration for using wgpu with eframe or the egui-wgpu winit feature.
|
/// Configuration for using wgpu with eframe or the egui-wgpu winit feature.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WgpuConfiguration {
|
pub struct WgpuConfiguration {
|
||||||
/// Present mode used for the primary surface.
|
/// Runtime-mutable configuration for the surface (present mode, frame latency).
|
||||||
pub present_mode: wgpu::PresentMode,
|
|
||||||
|
|
||||||
/// Desired maximum number of frames that the presentation engine should queue in advance.
|
|
||||||
///
|
///
|
||||||
/// Use `1` for low-latency, and `2` for high-throughput.
|
/// These are the fields exposed via [`RenderState::surface_config`] for live
|
||||||
///
|
/// reconfiguration at runtime.
|
||||||
/// See [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`] for details.
|
pub surface: SurfaceConfig,
|
||||||
///
|
|
||||||
/// `None` = `wgpu` default.
|
|
||||||
pub desired_maximum_frame_latency: Option<u32>,
|
|
||||||
|
|
||||||
/// How to create the wgpu adapter & device
|
/// How to create the wgpu adapter & device
|
||||||
pub wgpu_setup: WgpuSetup,
|
pub wgpu_setup: WgpuSetup,
|
||||||
@@ -308,31 +345,29 @@ fn wgpu_config_impl_send_sync() {
|
|||||||
impl std::fmt::Debug for WgpuConfiguration {
|
impl std::fmt::Debug for WgpuConfiguration {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let Self {
|
let Self {
|
||||||
present_mode,
|
surface,
|
||||||
desired_maximum_frame_latency,
|
|
||||||
wgpu_setup,
|
wgpu_setup,
|
||||||
on_surface_status: _,
|
on_surface_status: _,
|
||||||
} = self;
|
} = self;
|
||||||
f.debug_struct("WgpuConfiguration")
|
f.debug_struct("WgpuConfiguration")
|
||||||
.field("present_mode", &present_mode)
|
.field("surface", &surface)
|
||||||
.field(
|
|
||||||
"desired_maximum_frame_latency",
|
|
||||||
&desired_maximum_frame_latency,
|
|
||||||
)
|
|
||||||
.field("wgpu_setup", &wgpu_setup)
|
.field("wgpu_setup", &wgpu_setup)
|
||||||
.finish_non_exhaustive()
|
.finish_non_exhaustive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WgpuConfiguration {
|
||||||
|
#[inline]
|
||||||
|
pub fn with_surface_config(mut self, surface_config: SurfaceConfig) -> Self {
|
||||||
|
self.surface = surface_config;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for WgpuConfiguration {
|
impl Default for WgpuConfiguration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
present_mode: wgpu::PresentMode::AutoVsync,
|
surface: SurfaceConfig::HIGH_THROUGHPUT,
|
||||||
desired_maximum_frame_latency: if cfg!(target_os = "ios") {
|
|
||||||
None // The default is good on iOS, while `Some(1)` cuts FPS in half
|
|
||||||
} else {
|
|
||||||
Some(1) // Low-latency by default.
|
|
||||||
},
|
|
||||||
|
|
||||||
// No display handle available at this point — callers should replace this with
|
// No display handle available at this point — callers should replace this with
|
||||||
// `WgpuSetup::from_display_handle(...)` before creating the instance if one is available.
|
// `WgpuSetup::from_display_handle(...)` before creating the instance if one is available.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps
|
#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps
|
||||||
#![expect(unsafe_code)]
|
#![expect(unsafe_code)]
|
||||||
|
|
||||||
use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer};
|
use crate::{RenderState, SurfaceConfig, SurfaceErrorAction, WgpuConfiguration, renderer};
|
||||||
use crate::{
|
use crate::{
|
||||||
RendererOptions,
|
RendererOptions,
|
||||||
capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel},
|
capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel},
|
||||||
@@ -27,7 +27,7 @@ struct SurfaceState {
|
|||||||
/// NOTE: all egui viewports share the same painter.
|
/// NOTE: all egui viewports share the same painter.
|
||||||
pub struct Painter {
|
pub struct Painter {
|
||||||
context: Context,
|
context: Context,
|
||||||
configuration: WgpuConfiguration,
|
config: WgpuConfiguration,
|
||||||
options: RendererOptions,
|
options: RendererOptions,
|
||||||
support_transparent_backbuffer: bool,
|
support_transparent_backbuffer: bool,
|
||||||
screen_capture_state: Option<CaptureState>,
|
screen_capture_state: Option<CaptureState>,
|
||||||
@@ -58,16 +58,16 @@ impl Painter {
|
|||||||
/// associated.
|
/// associated.
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
context: Context,
|
context: Context,
|
||||||
configuration: WgpuConfiguration,
|
config: WgpuConfiguration,
|
||||||
support_transparent_backbuffer: bool,
|
support_transparent_backbuffer: bool,
|
||||||
options: RendererOptions,
|
options: RendererOptions,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (capture_tx, capture_rx) = capture_channel();
|
let (capture_tx, capture_rx) = capture_channel();
|
||||||
let instance = configuration.wgpu_setup.new_instance().await;
|
let instance = config.wgpu_setup.new_instance().await;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
context,
|
context,
|
||||||
configuration,
|
config,
|
||||||
options,
|
options,
|
||||||
support_transparent_backbuffer,
|
support_transparent_backbuffer,
|
||||||
screen_capture_state: None,
|
screen_capture_state: None,
|
||||||
@@ -94,17 +94,22 @@ impl Painter {
|
|||||||
fn configure_surface(
|
fn configure_surface(
|
||||||
surface_state: &SurfaceState,
|
surface_state: &SurfaceState,
|
||||||
render_state: &RenderState,
|
render_state: &RenderState,
|
||||||
config: &WgpuConfiguration,
|
config: &SurfaceConfig,
|
||||||
) {
|
) {
|
||||||
profiling::function_scope!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
|
let SurfaceConfig {
|
||||||
|
present_mode,
|
||||||
|
desired_maximum_frame_latency,
|
||||||
|
} = *config;
|
||||||
|
|
||||||
let width = surface_state.width;
|
let width = surface_state.width;
|
||||||
let height = surface_state.height;
|
let height = surface_state.height;
|
||||||
|
|
||||||
let mut surf_config = wgpu::SurfaceConfiguration {
|
let mut surf_config = wgpu::SurfaceConfiguration {
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
format: render_state.target_format,
|
format: render_state.target_format,
|
||||||
present_mode: config.present_mode,
|
present_mode,
|
||||||
alpha_mode: surface_state.alpha_mode,
|
alpha_mode: surface_state.alpha_mode,
|
||||||
view_formats: vec![render_state.target_format],
|
view_formats: vec![render_state.target_format],
|
||||||
..surface_state
|
..surface_state
|
||||||
@@ -113,7 +118,7 @@ impl Painter {
|
|||||||
.expect("The surface isn't supported by this adapter")
|
.expect("The surface isn't supported by this adapter")
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(desired_maximum_frame_latency) = config.desired_maximum_frame_latency {
|
if let Some(desired_maximum_frame_latency) = desired_maximum_frame_latency {
|
||||||
surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency;
|
surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,13 +206,9 @@ impl Painter {
|
|||||||
let render_state = if let Some(render_state) = &self.render_state {
|
let render_state = if let Some(render_state) = &self.render_state {
|
||||||
render_state
|
render_state
|
||||||
} else {
|
} else {
|
||||||
let render_state = RenderState::create(
|
let render_state =
|
||||||
&self.configuration,
|
RenderState::create(&self.config, &self.instance, Some(&surface), self.options)
|
||||||
&self.instance,
|
.await?;
|
||||||
Some(&surface),
|
|
||||||
self.options,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
self.render_state.get_or_insert(render_state)
|
self.render_state.get_or_insert(render_state)
|
||||||
};
|
};
|
||||||
let alpha_mode = if self.support_transparent_backbuffer {
|
let alpha_mode = if self.support_transparent_backbuffer {
|
||||||
@@ -278,7 +279,7 @@ impl Painter {
|
|||||||
surface_state.width = width;
|
surface_state.width = width;
|
||||||
surface_state.height = height;
|
surface_state.height = height;
|
||||||
|
|
||||||
Self::configure_surface(surface_state, render_state, &self.configuration);
|
Self::configure_surface(surface_state, render_state, &self.config.surface);
|
||||||
|
|
||||||
if let Some(depth_format) = self.options.depth_stencil_format {
|
if let Some(depth_format) = self.options.depth_stencil_format {
|
||||||
self.depth_texture_view.insert(
|
self.depth_texture_view.insert(
|
||||||
@@ -375,7 +376,7 @@ impl Painter {
|
|||||||
Self::configure_surface(
|
Self::configure_surface(
|
||||||
state,
|
state,
|
||||||
self.render_state.as_ref().unwrap(),
|
self.render_state.as_ref().unwrap(),
|
||||||
&self.configuration,
|
&self.config.surface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -449,6 +450,20 @@ impl Painter {
|
|||||||
let capture = !capture_data.is_empty();
|
let capture = !capture_data.is_empty();
|
||||||
let mut vsync_sec = 0.0;
|
let mut vsync_sec = 0.0;
|
||||||
|
|
||||||
|
// Apply any runtime changes requested via `RenderState::surface_config`.
|
||||||
|
// We diff against the already-applied values in `self.config.surface`
|
||||||
|
// and, if anything differs, mark every surface as needing reconfiguration so
|
||||||
|
// the existing `needs_reconfigure` pathway below picks them up.
|
||||||
|
if let Some(render_state) = self.render_state.as_ref()
|
||||||
|
&& render_state.surface_config != self.config.surface
|
||||||
|
{
|
||||||
|
self.config.surface = render_state.surface_config;
|
||||||
|
#[expect(clippy::iter_over_hash_type)]
|
||||||
|
for surface in self.surfaces.values_mut() {
|
||||||
|
surface.needs_reconfigure = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let Some(render_state) = self.render_state.as_mut() else {
|
let Some(render_state) = self.render_state.as_mut() else {
|
||||||
return vsync_sec;
|
return vsync_sec;
|
||||||
};
|
};
|
||||||
@@ -496,7 +511,7 @@ impl Painter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if surface_state.needs_reconfigure {
|
if surface_state.needs_reconfigure {
|
||||||
Self::configure_surface(surface_state, render_state, &self.configuration);
|
Self::configure_surface(surface_state, render_state, &self.config.surface);
|
||||||
surface_state.needs_reconfigure = false;
|
surface_state.needs_reconfigure = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,9 +531,9 @@ impl Painter {
|
|||||||
frame
|
frame
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
match (*self.configuration.on_surface_status)(&other) {
|
match (*self.config.on_surface_status)(&other) {
|
||||||
SurfaceErrorAction::RecreateSurface => {
|
SurfaceErrorAction::RecreateSurface => {
|
||||||
Self::configure_surface(surface_state, render_state, &self.configuration);
|
Self::configure_surface(surface_state, render_state, &self.config.surface);
|
||||||
}
|
}
|
||||||
SurfaceErrorAction::SkipFrame => {}
|
SurfaceErrorAction::SkipFrame => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,6 +310,11 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
|||||||
ui.end_row();
|
ui.end_row();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(mut cfg) = _frame.wgpu_surface_config() {
|
||||||
|
wgpu_surface_config_ui(ui, &mut cfg);
|
||||||
|
_frame.set_wgpu_surface_config(cfg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
@@ -357,6 +362,52 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "wgpu")]
|
||||||
|
fn wgpu_surface_config_ui(ui: &mut egui::Ui, cfg: &mut eframe::SurfaceConfig) {
|
||||||
|
use eframe::wgpu::PresentMode;
|
||||||
|
|
||||||
|
egui::Grid::new("wgpu_surface_config")
|
||||||
|
.num_columns(2)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label("Present mode:");
|
||||||
|
egui::ComboBox::from_id_salt("wgpu_present_mode")
|
||||||
|
.selected_text(format!("{:?}", cfg.present_mode))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for mode in [
|
||||||
|
PresentMode::AutoVsync,
|
||||||
|
PresentMode::AutoNoVsync,
|
||||||
|
PresentMode::Fifo,
|
||||||
|
PresentMode::FifoRelaxed,
|
||||||
|
PresentMode::Immediate,
|
||||||
|
PresentMode::Mailbox,
|
||||||
|
] {
|
||||||
|
ui.selectable_value(&mut cfg.present_mode, mode, format!("{mode:?}"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Desired max frame latency:");
|
||||||
|
egui::ComboBox::from_id_salt("wgpu_desired_max_frame_latency")
|
||||||
|
.selected_text(match cfg.desired_maximum_frame_latency {
|
||||||
|
None => "Default".to_owned(),
|
||||||
|
Some(n) => n.to_string(),
|
||||||
|
})
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
ui.weak("Lower value = lower latency");
|
||||||
|
ui.selectable_value(&mut cfg.desired_maximum_frame_latency, None, "Default");
|
||||||
|
for n in [0_u32, 1, 2, 3] {
|
||||||
|
ui.selectable_value(
|
||||||
|
&mut cfg.desired_maximum_frame_latency,
|
||||||
|
Some(n),
|
||||||
|
n.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ui.weak("Higher value = higher throughput/FPS");
|
||||||
|
});
|
||||||
|
ui.end_row();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
|||||||
Reference in New Issue
Block a user