Retain surface textures for animations
This commit is contained in:
parent
3540cdc4be
commit
fba9d65ba1
8 changed files with 365 additions and 19 deletions
|
|
@ -101,6 +101,17 @@ Tests:
|
|||
|
||||
Goal: freeze visual contents during movement and enable spawn-out.
|
||||
|
||||
Initial retained-record implementation status:
|
||||
|
||||
- Tiled animation can retain GPU/dmabuf-backed XDG and Xwayland surface trees.
|
||||
- Retained records hold both `GfxTexture` and `SurfaceBuffer` references so the
|
||||
existing buffer release/sync path remains authoritative.
|
||||
- Single-pixel buffers can be retained as color records.
|
||||
- Async SHM textures are not retained yet because Wry's per-surface SHM
|
||||
front/back textures can be reused by later commits while an animation is still
|
||||
running. Those surfaces fall back to live rendering until an explicit offscreen
|
||||
copy fallback exists.
|
||||
|
||||
Implementation shape:
|
||||
|
||||
- Add a retained render-record tree for toplevel surfaces.
|
||||
|
|
|
|||
139
src/animation.rs
139
src/animation.rs
|
|
@ -1,7 +1,11 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::{cmm_description::ColorDescription, cmm_render_intent::RenderIntent},
|
||||
gfx_api::{GfxTexture, SampleRect},
|
||||
ifs::wl_surface::{SurfaceBuffer, WlSurface},
|
||||
rect::Rect,
|
||||
state::State,
|
||||
theme::Color,
|
||||
tree::{LatchListener, NodeId, OutputNode},
|
||||
utils::{clonecell::CloneCell, event_listener::EventListener},
|
||||
},
|
||||
|
|
@ -54,6 +58,112 @@ pub struct AnimationState {
|
|||
tick: CloneCell<Option<Rc<AnimationTick>>>,
|
||||
}
|
||||
|
||||
pub struct RetainedToplevel {
|
||||
pub offset: (i32, i32),
|
||||
pub surface: RetainedSurface,
|
||||
}
|
||||
|
||||
pub struct RetainedSurface {
|
||||
pub offset: (i32, i32),
|
||||
pub size: (i32, i32),
|
||||
pub content: RetainedContent,
|
||||
pub below: Vec<RetainedSurface>,
|
||||
pub above: Vec<RetainedSurface>,
|
||||
}
|
||||
|
||||
pub enum RetainedContent {
|
||||
Texture {
|
||||
texture: Rc<dyn GfxTexture>,
|
||||
buffer: Rc<SurfaceBuffer>,
|
||||
source: SampleRect,
|
||||
alpha: Option<f32>,
|
||||
color_description: Rc<ColorDescription>,
|
||||
render_intent: RenderIntent,
|
||||
alpha_mode: crate::gfx_api::AlphaMode,
|
||||
opaque: bool,
|
||||
},
|
||||
Color {
|
||||
color: Color,
|
||||
alpha: Option<f32>,
|
||||
color_description: Rc<ColorDescription>,
|
||||
render_intent: RenderIntent,
|
||||
},
|
||||
}
|
||||
|
||||
impl RetainedToplevel {
|
||||
pub fn capture_surface(surface: &WlSurface, offset: (i32, i32)) -> Option<Rc<Self>> {
|
||||
Some(Rc::new(Self {
|
||||
offset,
|
||||
surface: RetainedSurface::capture(surface, (0, 0))?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl RetainedSurface {
|
||||
fn capture(surface: &WlSurface, offset: (i32, i32)) -> Option<Self> {
|
||||
let buffer = surface.buffer.get()?;
|
||||
let size = surface.buffer_abs_pos.get().size();
|
||||
let source = *surface.buffer_points_norm.borrow();
|
||||
let color_description = surface.color_description();
|
||||
let render_intent = surface.render_intent();
|
||||
let alpha_mode = surface.alpha_mode();
|
||||
let alpha = surface.alpha();
|
||||
let content = match buffer.buffer.buf.get_stable_texture() {
|
||||
Some(texture) => RetainedContent::Texture {
|
||||
opaque: surface.opaque(),
|
||||
texture,
|
||||
buffer,
|
||||
source,
|
||||
alpha,
|
||||
color_description,
|
||||
render_intent,
|
||||
alpha_mode,
|
||||
},
|
||||
None => {
|
||||
let color = buffer.buffer.buf.color?;
|
||||
RetainedContent::Color {
|
||||
color: Color::from_u32(
|
||||
color_description.eotf,
|
||||
alpha_mode,
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
color[3],
|
||||
),
|
||||
alpha,
|
||||
color_description,
|
||||
render_intent,
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut below = vec![];
|
||||
let mut above = vec![];
|
||||
if let Some(children) = surface.children.borrow().as_deref() {
|
||||
for child in children.below.iter() {
|
||||
if child.pending.get() {
|
||||
continue;
|
||||
}
|
||||
let pos = child.sub_surface.position.get();
|
||||
below.push(Self::capture(&child.sub_surface.surface, pos)?);
|
||||
}
|
||||
for child in children.above.iter() {
|
||||
if child.pending.get() {
|
||||
continue;
|
||||
}
|
||||
let pos = child.sub_surface.position.get();
|
||||
above.push(Self::capture(&child.sub_surface.surface, pos)?);
|
||||
}
|
||||
}
|
||||
Some(Self {
|
||||
offset,
|
||||
size,
|
||||
content,
|
||||
below,
|
||||
above,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AnimationState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
|
@ -79,6 +189,7 @@ impl AnimationState {
|
|||
node_id: NodeId,
|
||||
old: Rect,
|
||||
new: Rect,
|
||||
retained: Option<Rc<RetainedToplevel>>,
|
||||
now_nsec: u64,
|
||||
duration_ms: u32,
|
||||
curve: AnimationCurve,
|
||||
|
|
@ -89,10 +200,10 @@ impl AnimationState {
|
|||
}
|
||||
let duration_nsec = duration_ms as u64 * 1_000_000;
|
||||
let mut windows = self.windows.borrow_mut();
|
||||
let from = match windows.get(&node_id) {
|
||||
let (from, retained) = match windows.get(&node_id) {
|
||||
Some(anim) if anim.to == new => return false,
|
||||
Some(anim) => anim.rect_at(now_nsec),
|
||||
None => old,
|
||||
Some(anim) => (anim.rect_at(now_nsec), anim.retained.clone().or(retained)),
|
||||
None => (old, retained),
|
||||
};
|
||||
if from == new {
|
||||
windows.remove(&node_id);
|
||||
|
|
@ -107,6 +218,7 @@ impl AnimationState {
|
|||
duration_nsec,
|
||||
curve,
|
||||
last_damage: from,
|
||||
retained,
|
||||
},
|
||||
);
|
||||
true
|
||||
|
|
@ -120,6 +232,18 @@ impl AnimationState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn retained_snapshot(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
now_nsec: u64,
|
||||
) -> Option<Rc<RetainedToplevel>> {
|
||||
let windows = self.windows.borrow();
|
||||
match windows.get(&node_id) {
|
||||
Some(anim) if !anim.done(now_nsec) => anim.retained.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn damage_active(&self, state: &State, now_nsec: u64) -> bool {
|
||||
let mut damages = vec![];
|
||||
let mut any_active = false;
|
||||
|
|
@ -164,6 +288,7 @@ struct WindowAnimation {
|
|||
duration_nsec: u64,
|
||||
curve: AnimationCurve,
|
||||
last_damage: Rect,
|
||||
retained: Option<Rc<RetainedToplevel>>,
|
||||
}
|
||||
|
||||
impl WindowAnimation {
|
||||
|
|
@ -286,8 +411,8 @@ mod tests {
|
|||
let id = NodeId(1);
|
||||
let a = Rect::new_sized_saturating(0, 0, 100, 100);
|
||||
let b = Rect::new_sized_saturating(100, 0, 100, 100);
|
||||
assert!(state.set_target(id, a, b, 0, 160, AnimationCurve::Linear));
|
||||
assert!(!state.set_target(id, a, b, 80_000_000, 160, AnimationCurve::Linear));
|
||||
assert!(state.set_target(id, a, b, None, 0, 160, AnimationCurve::Linear));
|
||||
assert!(!state.set_target(id, a, b, None, 80_000_000, 160, AnimationCurve::Linear));
|
||||
assert_eq!(
|
||||
state.visual_rect(id, b, 80_000_000),
|
||||
Rect::new_sized_saturating(50, 0, 100, 100)
|
||||
|
|
@ -301,8 +426,8 @@ mod tests {
|
|||
let a = Rect::new_sized_saturating(0, 0, 100, 100);
|
||||
let b = Rect::new_sized_saturating(100, 0, 100, 100);
|
||||
let c = Rect::new_sized_saturating(200, 0, 100, 100);
|
||||
assert!(state.set_target(id, a, b, 0, 160, AnimationCurve::Linear));
|
||||
assert!(state.set_target(id, a, c, 80_000_000, 160, AnimationCurve::Linear));
|
||||
assert!(state.set_target(id, a, b, None, 0, 160, AnimationCurve::Linear));
|
||||
assert!(state.set_target(id, a, c, None, 80_000_000, 160, AnimationCurve::Linear));
|
||||
assert_eq!(
|
||||
state.visual_rect(id, c, 80_000_000),
|
||||
Rect::new_sized_saturating(50, 0, 100, 100)
|
||||
|
|
|
|||
|
|
@ -310,6 +310,19 @@ impl WlBuffer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_stable_texture(&self) -> Option<Rc<dyn GfxTexture>> {
|
||||
match &*self.storage.borrow() {
|
||||
None => None,
|
||||
Some(s) => match s {
|
||||
WlBufferStorage::Shm {
|
||||
dmabuf_buffer_params,
|
||||
..
|
||||
} => dmabuf_buffer_params.tex.clone(),
|
||||
WlBufferStorage::Dmabuf { tex, .. } => tex.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_texture_or_log(&self, surface: &WlSurface, sync_shm: bool) {
|
||||
if let Err(e) = self.update_texture(surface, sync_shm) {
|
||||
log::warn!("Could not update texture: {}", ErrorFmt(e));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
animation::RetainedToplevel,
|
||||
client::Client,
|
||||
cursor::KnownCursor,
|
||||
fixed::Fixed,
|
||||
|
|
@ -514,6 +515,10 @@ impl ToplevelNodeBase for Xwindow {
|
|||
Some(self.x.surface.clone())
|
||||
}
|
||||
|
||||
fn tl_animation_snapshot(&self) -> Option<Rc<RetainedToplevel>> {
|
||||
RetainedToplevel::capture_surface(&self.x.surface, (0, 0))
|
||||
}
|
||||
|
||||
fn tl_admits_children(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ pub mod xdg_dialog_v1;
|
|||
|
||||
use {
|
||||
crate::{
|
||||
animation::RetainedToplevel,
|
||||
bugs,
|
||||
bugs::Bugs,
|
||||
client::{Client, ClientError},
|
||||
|
|
@ -779,6 +780,11 @@ impl ToplevelNodeBase for XdgToplevel {
|
|||
Some(self.xdg.surface.clone())
|
||||
}
|
||||
|
||||
fn tl_animation_snapshot(&self) -> Option<Rc<RetainedToplevel>> {
|
||||
let geo = self.xdg.geometry();
|
||||
RetainedToplevel::capture_surface(&self.xdg.surface, (-geo.x1(), -geo.y1()))
|
||||
}
|
||||
|
||||
fn tl_restack_popups(&self) {
|
||||
self.xdg.restack_popups();
|
||||
}
|
||||
|
|
|
|||
182
src/renderer.rs
182
src/renderer.rs
|
|
@ -1,7 +1,8 @@
|
|||
use {
|
||||
crate::{
|
||||
animation::{RetainedContent, RetainedSurface, RetainedToplevel},
|
||||
cmm::cmm_render_intent::RenderIntent,
|
||||
gfx_api::{AcquireSync, AlphaMode, GfxApiOpt, ReleaseSync, SampleRect},
|
||||
gfx_api::{AcquireSync, AlphaMode, BufferResv, GfxApiOpt, ReleaseSync, SampleRect},
|
||||
ifs::wl_surface::{
|
||||
SurfaceBuffer, WlSurface,
|
||||
x_surface::xwindow::Xwindow,
|
||||
|
|
@ -467,6 +468,167 @@ impl Renderer<'_> {
|
|||
visual.move_(-container.abs_x1.get(), -container.abs_y1.get())
|
||||
}
|
||||
|
||||
fn render_child_or_snapshot(
|
||||
&mut self,
|
||||
child: &Rc<dyn ToplevelNode>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
bounds: Option<&Rect>,
|
||||
) {
|
||||
if let Some(retained) = self
|
||||
.state
|
||||
.animations
|
||||
.retained_snapshot(child.node_id(), self.state.now_nsec())
|
||||
{
|
||||
self.render_retained_toplevel(&retained, x, y, bounds);
|
||||
} else {
|
||||
child.node_render(self, x, y, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_retained_toplevel(
|
||||
&mut self,
|
||||
retained: &RetainedToplevel,
|
||||
x: i32,
|
||||
y: i32,
|
||||
bounds: Option<&Rect>,
|
||||
) {
|
||||
let (x, y) = self
|
||||
.base
|
||||
.scale_point(x + retained.offset.0, y + retained.offset.1);
|
||||
self.render_retained_surface_scaled(&retained.surface, x, y, None, bounds);
|
||||
}
|
||||
|
||||
fn render_retained_surface_scaled(
|
||||
&mut self,
|
||||
retained: &RetainedSurface,
|
||||
x: i32,
|
||||
y: i32,
|
||||
pos_rel: Option<(i32, i32)>,
|
||||
bounds: Option<&Rect>,
|
||||
) {
|
||||
let stretch = self.stretch.take();
|
||||
let corner_radius = self.corner_radius.take();
|
||||
let mut size = retained.size;
|
||||
if let Some((x_rel, y_rel)) = pos_rel {
|
||||
let (x, y) = self.base.scale_point(x_rel, y_rel);
|
||||
let (w, h) = self.base.scale_point(x_rel + size.0, y_rel + size.1);
|
||||
size = (w - x, h - y);
|
||||
} else {
|
||||
size = self.base.scale_point(size.0, size.1);
|
||||
}
|
||||
let mut stretched_source = None;
|
||||
if let Some(s) = stretch {
|
||||
if let RetainedContent::Texture { source, .. } = &retained.content {
|
||||
let mut source = *source;
|
||||
if size.0 > 0 && size.1 > 0 {
|
||||
let sx = s.0 as f32 / size.0 as f32;
|
||||
let sy = s.1 as f32 / size.1 as f32;
|
||||
source.x2 *= sx;
|
||||
source.y2 *= sy;
|
||||
}
|
||||
stretched_source = Some(source);
|
||||
}
|
||||
size = s;
|
||||
}
|
||||
for child in &retained.below {
|
||||
let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1);
|
||||
self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds);
|
||||
}
|
||||
self.corner_radius = corner_radius;
|
||||
self.render_retained_content(retained, stretched_source, x, y, size, bounds);
|
||||
for child in &retained.above {
|
||||
let (x1, y1) = self.base.scale_point(child.offset.0, child.offset.1);
|
||||
self.render_retained_surface_scaled(child, x + x1, y + y1, Some(child.offset), bounds);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_retained_content(
|
||||
&mut self,
|
||||
retained: &RetainedSurface,
|
||||
stretched_source: Option<SampleRect>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
size: (i32, i32),
|
||||
bounds: Option<&Rect>,
|
||||
) {
|
||||
let corner_radius = self.corner_radius.take();
|
||||
match &retained.content {
|
||||
RetainedContent::Texture {
|
||||
texture,
|
||||
buffer,
|
||||
source,
|
||||
alpha,
|
||||
color_description,
|
||||
render_intent,
|
||||
alpha_mode,
|
||||
opaque,
|
||||
} => {
|
||||
let source = stretched_source.unwrap_or(*source);
|
||||
if let Some(cr) = corner_radius {
|
||||
self.base.render_rounded_texture(
|
||||
texture,
|
||||
*alpha,
|
||||
x,
|
||||
y,
|
||||
Some(source),
|
||||
Some(size),
|
||||
self.base.scale,
|
||||
bounds,
|
||||
Some(buffer.clone() as Rc<dyn BufferResv>),
|
||||
AcquireSync::Unnecessary,
|
||||
buffer.release_sync,
|
||||
color_description,
|
||||
*render_intent,
|
||||
*alpha_mode,
|
||||
cr,
|
||||
);
|
||||
} else {
|
||||
self.base.render_texture(
|
||||
texture,
|
||||
*alpha,
|
||||
x,
|
||||
y,
|
||||
Some(source),
|
||||
Some(size),
|
||||
self.base.scale,
|
||||
bounds,
|
||||
Some(buffer.clone() as Rc<dyn BufferResv>),
|
||||
AcquireSync::Unnecessary,
|
||||
buffer.release_sync,
|
||||
*opaque,
|
||||
color_description,
|
||||
*render_intent,
|
||||
*alpha_mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
RetainedContent::Color {
|
||||
color,
|
||||
alpha,
|
||||
color_description,
|
||||
render_intent,
|
||||
} => {
|
||||
if let Some(rect) = Rect::new_sized(x, y, size.0, size.1) {
|
||||
let rect = match bounds {
|
||||
None => rect,
|
||||
Some(bounds) => rect.intersect(*bounds),
|
||||
};
|
||||
if !rect.is_empty() {
|
||||
self.base.sync();
|
||||
self.base.fill_scaled_boxes(
|
||||
&[rect],
|
||||
color,
|
||||
*alpha,
|
||||
&color_description.linear,
|
||||
*render_intent,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
|
||||
self.render_container_decorations(container, x, y);
|
||||
|
||||
|
|
@ -526,9 +688,12 @@ impl Renderer<'_> {
|
|||
self.corner_radius = Some(inner_cr);
|
||||
}
|
||||
}
|
||||
child
|
||||
.node
|
||||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||
self.render_child_or_snapshot(
|
||||
&child.node,
|
||||
x + content.x1(),
|
||||
y + content.y1(),
|
||||
Some(&body),
|
||||
);
|
||||
self.stretch = None;
|
||||
self.corner_radius = None;
|
||||
} else {
|
||||
|
|
@ -579,9 +744,12 @@ impl Renderer<'_> {
|
|||
}
|
||||
let body = body.move_(x, y);
|
||||
let body = self.base.scale_rect(body);
|
||||
child
|
||||
.node
|
||||
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
|
||||
self.render_child_or_snapshot(
|
||||
&child.node,
|
||||
x + content.x1(),
|
||||
y + content.y1(),
|
||||
Some(&body),
|
||||
);
|
||||
self.stretch = None;
|
||||
self.corner_radius = None;
|
||||
}
|
||||
|
|
|
|||
13
src/state.rs
13
src/state.rs
|
|
@ -2,7 +2,9 @@ use {
|
|||
crate::{
|
||||
acceptor::Acceptor,
|
||||
allocator::BufferObject,
|
||||
animation::{AnimationCurve, AnimationState, AnimationTick, expand_damage_rect},
|
||||
animation::{
|
||||
AnimationCurve, AnimationState, AnimationTick, RetainedToplevel, expand_damage_rect,
|
||||
},
|
||||
async_engine::{AsyncEngine, SpawnedFuture},
|
||||
backend::{
|
||||
Backend, BackendConnectorState, BackendConnectorStateSerials, BackendDrmDevice,
|
||||
|
|
@ -1469,7 +1471,13 @@ impl State {
|
|||
self.eng.now().msec()
|
||||
}
|
||||
|
||||
pub fn queue_tiled_animation(self: &Rc<Self>, node_id: NodeId, old: Rect, new: Rect) {
|
||||
pub fn queue_tiled_animation(
|
||||
self: &Rc<Self>,
|
||||
node_id: NodeId,
|
||||
old: Rect,
|
||||
new: Rect,
|
||||
retained: Option<Rc<RetainedToplevel>>,
|
||||
) {
|
||||
if !self.animations.enabled.get()
|
||||
|| !self.layout_animations_active.get()
|
||||
|| self.suppress_animations_for_next_layout.get()
|
||||
|
|
@ -1494,6 +1502,7 @@ impl State {
|
|||
node_id,
|
||||
old,
|
||||
new,
|
||||
retained,
|
||||
now,
|
||||
self.animations.duration_ms.get(),
|
||||
self.animations.curve.get(),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
animation::RetainedToplevel,
|
||||
client::{Client, ClientId},
|
||||
criteria::{
|
||||
CritDestroyListener, CritMatcherId,
|
||||
|
|
@ -197,9 +198,12 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
|||
&& !self.node_is_container()
|
||||
&& !parent_is_mono
|
||||
{
|
||||
data.state
|
||||
.clone()
|
||||
.queue_tiled_animation(data.node_id, prev, *rect);
|
||||
data.state.clone().queue_tiled_animation(
|
||||
data.node_id,
|
||||
prev,
|
||||
*rect,
|
||||
self.tl_animation_snapshot(),
|
||||
);
|
||||
}
|
||||
if prev.size() != rect.size() {
|
||||
for sc in data.jay_screencasts.lock().values() {
|
||||
|
|
@ -316,6 +320,11 @@ pub trait ToplevelNodeBase: Node {
|
|||
fn tl_scanout_surface(&self) -> Option<Rc<WlSurface>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn tl_animation_snapshot(&self) -> Option<Rc<RetainedToplevel>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn tl_restack_popups(&self) {
|
||||
// nothing
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue