Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions niri-config/src/window_rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ pub struct WindowRule {
pub scroll_factor: Option<FloatOrInt<0, 100>>,
#[knuffel(child, unwrap(argument))]
pub tiled_state: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub force_render: Option<bool>,
#[knuffel(child, unwrap(argument))]
pub force_render_fps: Option<u16>,
}

#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
Expand Down
158 changes: 152 additions & 6 deletions src/niri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ pub struct Niri {
/// Window ID for the "dynamic cast" special window for the xdp-gnome picker.
#[cfg(feature = "xdp-gnome-screencast")]
pub dynamic_cast_id_for_portal: MappedId,
force_render_state: RefCell<HashMap<u32, ForceRenderState>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -572,6 +573,20 @@ struct SurfaceFrameThrottlingState {
last_sent_at: RefCell<Option<(Output, u32)>>,
}

struct ForceRenderState {
last_time: Duration,
is_delay: bool,
}

impl Default for ForceRenderState {
fn default() -> Self {
Self {
last_time: Duration::ZERO,
is_delay: false,
}
}
}

pub enum CenterCoords {
Separately,
Both,
Expand Down Expand Up @@ -2822,6 +2837,7 @@ impl Niri {

#[cfg(feature = "xdp-gnome-screencast")]
dynamic_cast_id_for_portal: MappedId::next(),
force_render_state: RefCell::new(HashMap::new()),
};

niri.reset_pointer_inactivity_timer();
Expand Down Expand Up @@ -4956,6 +4972,46 @@ impl Niri {
}
}

pub fn delay_to_send_frame_callbacks(
&mut self,
surface: WlSurface,
output: Output,
interval: Duration,
) {
let timer = Timer::from_duration(interval);
self.event_loop
.insert_source(timer, move |_, _, state| {
state
.niri
.send_frame_callback_for_surface(surface.clone(), &output);
TimeoutAction::Drop
})
.unwrap();
}

fn send_frame_callback_for_surface(&mut self, surface: WlSurface, output: &Output) {
let frame_callback_time = get_monotonic_time();
let mut force_render_state_borrow = self.force_render_state.borrow_mut();

let force_render_state = force_render_state_borrow
.entry(surface.id().protocol_id())
.or_default();

// reset the delay state
force_render_state.last_time = frame_callback_time;
force_render_state.is_delay = false;

debug!("Sending frame callback for surface");

send_frames_surface_tree(
&surface,
output,
frame_callback_time,
FRAME_CALLBACK_THROTTLE,
|_, _| Some(output.clone()),
);
}

pub fn send_frame_callbacks(&mut self, output: &Output) {
let _span = tracy_client::span!("Niri::send_frame_callbacks");

Expand Down Expand Up @@ -4997,13 +5053,97 @@ impl Niri {

let frame_callback_time = get_monotonic_time();

// Collect delayed surfaces
let delayed_surfaces = RefCell::new(HashMap::new());

for mapped in self.layout.windows_for_output_mut(output) {
mapped.send_frame(
output,
frame_callback_time,
FRAME_CALLBACK_THROTTLE,
should_send,
);
// Check if the surface should be forced to render
if mapped.rules().force_render == Some(true) || mapped.is_window_cast_target() {
// Calculate delay time
let interval = if let Some(force_render_fps) = mapped.rules().force_render_fps {
Duration::from_secs_f64(1.0 / force_render_fps as f64)
} else {
Duration::ZERO
};

// Check if the surface should be forced to render
let should_force_render = |surface: &WlSurface, states: &SurfaceData| {
// Check if the surface is on the primary output.
let mut on_primary = true;
let current_primary_output = surface_primary_scanout_output(surface, states);
if current_primary_output.as_ref() != Some(output) {
on_primary = false;
}

let mut force_render_state_borrow = self.force_render_state.borrow_mut();

if !on_primary && !interval.is_zero() {
//fps limited branch
let force_render_state = force_render_state_borrow
.entry(surface.id().protocol_id())
.or_default();

if !force_render_state.is_delay {
// Time diff since last frame callback
let time_diff: Duration =
frame_callback_time.saturating_sub(force_render_state.last_time);

// Calculate next frame callback time and push needed data to delayed_surfaces
let delay_time = interval.saturating_sub(time_diff);
let mut delayed = delayed_surfaces.borrow_mut();
delayed
.entry(surface.id())
.or_insert((surface.clone(), delay_time));

// Set force render state
force_render_state.is_delay = true;
}
return None;
} else {
// Remove force render state if not on primary output
if on_primary {
force_render_state_borrow.remove(&surface.id().protocol_id());
}
//fps unlimited branch
let frame_throttling_state = states
.data_map
.get_or_insert(SurfaceFrameThrottlingState::default);

// Next, check the throttling status.
let mut last_sent_at = frame_throttling_state.last_sent_at.borrow_mut();
let mut send = true;

// If we already sent a frame callback to this surface this output refresh
// cycle, don't send one again to prevent empty-damage commit busy loops.
if let Some((last_output, last_sequence)) = &*last_sent_at {
if last_output == output && *last_sequence == sequence {
send = false;
}
}

if send {
*last_sent_at = Some((output.clone(), sequence));
Some(output.clone())
} else {
None
}
}
};

mapped.send_frame(
output,
frame_callback_time,
FRAME_CALLBACK_THROTTLE,
should_force_render,
);
} else {
mapped.send_frame(
output,
frame_callback_time,
FRAME_CALLBACK_THROTTLE,
should_send,
);
}
}

for surface in layer_map_for_output(output).layers() {
Expand Down Expand Up @@ -5044,6 +5184,12 @@ impl Niri {
should_send,
);
}

// Call delay timer
let delayed_map = delayed_surfaces.into_inner();
for (_, (surface, interval)) in delayed_map {
self.delay_to_send_frame_callbacks(surface, output.clone(), interval);
}
}

pub fn send_frame_callbacks_on_fallback_timer(&mut self) {
Expand Down
14 changes: 14 additions & 0 deletions src/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ pub struct ResolvedWindowRules {

/// Override whether to set the Tiled xdg-toplevel state on the window.
pub tiled_state: Option<bool>,

/// Whether to force a render on this window.
pub force_render: Option<bool>,

/// force render fps limit for this window.
pub force_render_fps: Option<u16>,
}

impl<'a> WindowRef<'a> {
Expand Down Expand Up @@ -296,6 +302,14 @@ impl ResolvedWindowRules {
if let Some(x) = rule.tiled_state {
resolved.tiled_state = Some(x);
}

if let Some(x) = rule.force_render {
resolved.force_render = Some(x);
}

if let Some(x) = rule.force_render_fps {
resolved.force_render_fps = Some(x);
}
}

resolved.open_on_output = open_on_output.map(|x| x.to_owned());
Expand Down