use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;
use smithay_client_toolkit::compositor::CompositorState;
use smithay_client_toolkit::shm::Shm;
use smithay_client_toolkit::shm::slot::SlotPool;
use smithay_client_toolkit::reexports::client::protocol::wl_shm;
use smithay_client_toolkit::reexports::client::protocol::wl_subsurface::WlSubsurface;
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
use crate::app::{ App, SubsurfaceParent };
use crate::egl_context::{ EglContext, EglSurface };
use crate::draw::paint_detached;
use crate::event_loop::{ AppData, SurfaceFocus };
use crate::event_loop::perf::RasterTrace;
use crate::render::Canvas;
use crate::widget::Element;
use super::present::{ apply_input_region, pick_shm_format };
fn to_logical( x: i32, y: i32, scale: u32 ) -> ( i32, i32 )
{
let s = scale.max( 1 ) as i32;
( x / s, y / s )
}
pub( crate ) struct SubsurfaceSlot
{
subsurface: WlSubsurface,
surface: WlSurface,
pool: Option<SlotPool>,
egl_surface: Option<EglSurface>,
canvas: Option<Canvas>,
last_pos: ( i32, i32 ),
last_size: ( u32, u32 ),
last_version: u64,
rastered: bool,
parent: SubsurfaceParent,
scroll_canvases: HashMap<usize, Canvas>,
viewport_canvases: HashMap<usize, Canvas>,
}
impl SubsurfaceSlot
{
pub( crate ) fn parent( &self ) -> SubsurfaceParent
{
self.parent
}
fn destroy( mut self, egl: Option<&Arc<EglContext>> )
{
if let ( Some( ctx ), Some( es ) ) = ( egl, self.egl_surface.as_ref() )
{
let _ = ctx.make_current( es );
}
self.scroll_canvases.clear();
self.viewport_canvases.clear();
self.egl_surface = None;
self.subsurface.destroy();
self.surface.destroy();
}
}
fn focus_of( parent: SubsurfaceParent ) -> SurfaceFocus
{
match parent
{
SubsurfaceParent::Main => SurfaceFocus::Main,
SubsurfaceParent::Overlay( id ) => SurfaceFocus::Overlay( id ),
}
}
fn parent_frame_pending<A: App>( data: &AppData<A>, parent: SubsurfaceParent ) -> bool
{
match parent
{
SubsurfaceParent::Main => data.main.frame_pending,
SubsurfaceParent::Overlay( id ) => data.overlays.get( &id ).is_some_and( |ss| ss.frame_pending ),
}
}
fn arm_parent_frame<A: App>( data: &mut AppData<A>, parent: SubsurfaceParent, wl: &WlSurface )
{
let ss = match parent
{
SubsurfaceParent::Main => &mut data.main,
SubsurfaceParent::Overlay( id ) => match data.overlays.get_mut( &id ) { Some( s ) => s, None => return },
};
ss.frame_tick_due = false;
if ss.frame_pending { return; }
let _ = wl.frame( &data.platform.qh, focus_of( parent ) );
ss.frame_pending = true;
}
fn keep_parent_cadence<A: App>( data: &mut AppData<A>, parent: SubsurfaceParent, animating: bool )
{
let ss = match parent
{
SubsurfaceParent::Main => &mut data.main,
SubsurfaceParent::Overlay( id ) => match data.overlays.get_mut( &id ) { Some( s ) => s, None => return },
};
if !std::mem::take( &mut ss.frame_tick_due ) { return; }
if ss.frame_pending || !animating { return; }
let Some( wl ) = ss.platform.surface.try_wl_surface().cloned() else { return };
let _ = wl.frame( &data.platform.qh, focus_of( parent ) );
wl.commit();
ss.frame_pending = true;
}
fn resolve_parent<A: App>( data: &AppData<A>, parent: SubsurfaceParent ) -> Option<( WlSurface, u32, u32, u32 )>
{
let ss = match parent
{
SubsurfaceParent::Main => &data.main,
SubsurfaceParent::Overlay( id ) => data.overlays.get( &id )?,
};
if !ss.configured { return None; }
if ss.width == 0 || ss.height == 0 { return None; }
let surface = ss.platform.surface.try_wl_surface()?.clone();
let scale = ss.buffer_scale() as u32;
Some( ( surface, ss.width * scale, ss.height * scale, scale ) )
}
fn mark_parent_dirty( list: &mut Vec<( SubsurfaceParent, WlSurface )>, parent: SubsurfaceParent, wl: &WlSurface )
{
if !list.iter().any( |( p, _ )| *p == parent )
{
list.push( ( parent, wl.clone() ) );
}
}
pub( crate ) fn reconcile_subsurfaces<A: App>( data: &mut AppData<A> )
{
if data.platform.subcompositor.is_none() { return; }
if !data.main.configured { return; }
let ( format, swap_rb ) = pick_shm_format( &data.platform.shm );
let specs: Vec<crate::app::SubsurfaceSpec<A::Message>> = data.app.subsurfaces();
let mut dirty_parents: Vec<( SubsurfaceParent, WlSurface )> = Vec::new();
let mut committed: Vec<crate::app::SubsurfaceId> = Vec::new();
let present: std::collections::HashSet<crate::app::SubsurfaceId> =
specs.iter().map( |s| s.id ).collect();
let stale: Vec<crate::app::SubsurfaceId> = data.platform.subsurfaces.keys()
.copied()
.filter( |id| !present.contains( id ) )
.collect();
for id in stale
{
if let Some( slot ) = data.platform.subsurfaces.remove( &id )
{
let parent = slot.parent;
slot.destroy( data.platform.egl_context.as_ref() );
if let Some( ( wl, _, _, _ ) ) = resolve_parent( data, parent )
{
mark_parent_dirty( &mut dirty_parents, parent, &wl );
}
}
}
for spec in &specs
{
let Some( ( parent_wl, pw, ph, scale ) ) = resolve_parent( data, spec.parent ) else { continue };
if let Entry::Vacant( vacant ) = data.platform.subsurfaces.entry( spec.id )
{
let ( subsurface, surface ) = {
let sc = data.platform.subcompositor.as_ref().unwrap();
sc.create_subsurface( parent_wl.clone(), &data.platform.qh )
};
subsurface.set_desync();
let ( lx, ly ) = to_logical( spec.x, spec.y, scale );
subsurface.set_position( lx, ly );
vacant.insert( SubsurfaceSlot {
subsurface,
surface,
pool: None,
egl_surface: None,
canvas: None,
last_pos: ( spec.x, spec.y ),
last_size: ( 0, 0 ),
last_version: u64::MAX,
rastered: false,
parent: spec.parent,
scroll_canvases: HashMap::new(),
viewport_canvases: HashMap::new(),
} );
mark_parent_dirty( &mut dirty_parents, spec.parent, &parent_wl );
}
let parent_pending = parent_frame_pending( data, spec.parent );
let size_changed = data.platform.subsurfaces.get( &spec.id )
.map( |s| s.last_size != ( pw, ph ) )
.unwrap_or( true );
let needs_raster = {
let slot = data.platform.subsurfaces.get( &spec.id ).unwrap();
!slot.rastered || size_changed || slot.last_version != spec.content_version
};
if needs_raster
{
let egl = if spec.gpu { data.platform.egl_context.as_ref() } else { None };
let slot = data.platform.subsurfaces.get_mut( &spec.id ).unwrap();
render_slot::<A::Message>(
slot, egl, &mut data.platform.subsurface_gles_canvas,
&data.platform.shm, &data.platform.compositor_state, &spec.view,
pw, ph, scale, format, swap_rb, size_changed,
);
slot.rastered = true;
slot.last_version = spec.content_version;
slot.last_size = ( pw, ph );
committed.push( spec.id );
mark_parent_dirty( &mut dirty_parents, spec.parent, &parent_wl );
}
let slot = data.platform.subsurfaces.get_mut( &spec.id ).unwrap();
if slot.last_pos != ( spec.x, spec.y ) && !parent_pending
{
let ( lx, ly ) = to_logical( spec.x, spec.y, scale );
slot.subsurface.set_position( lx, ly );
slot.surface.commit();
slot.last_pos = ( spec.x, spec.y );
mark_parent_dirty( &mut dirty_parents, spec.parent, &parent_wl );
}
}
for ( parent, wl ) in dirty_parents
{
if parent == SubsurfaceParent::Main && !data.first_frame_committed { continue; }
arm_parent_frame( data, parent, &wl );
wl.commit();
}
let animating = data.app.is_animating();
keep_parent_cadence( data, SubsurfaceParent::Main, animating );
let overlay_ids: Vec<crate::app::OverlayId> = data.overlays.keys().copied().collect();
for id in overlay_ids
{
keep_parent_cadence( data, SubsurfaceParent::Overlay( id ), animating );
}
for id in committed
{
data.app.on_subsurface_committed( id );
}
}
#[ allow( clippy::too_many_arguments ) ]
fn render_slot<Msg: Clone>(
slot: &mut SubsurfaceSlot,
egl_ctx: Option<&Arc<EglContext>>,
gles_canvas: &mut Option<Canvas>,
shm: &Shm,
compositor: &CompositorState,
view: &Element<Msg>,
pw: u32,
ph: u32,
scale: u32,
shm_format: wl_shm::Format,
swap_rb: bool,
size_changed: bool,
)
{
if let Some( ctx ) = egl_ctx
{
render_slot_gpu::<Msg>( slot, ctx, gles_canvas, compositor, view, pw, ph, scale, size_changed );
}
else
{
render_slot_software::<Msg>( slot, shm, compositor, view, pw, ph, scale, shm_format, swap_rb, size_changed );
}
}
#[ allow( clippy::too_many_arguments ) ]
fn render_slot_gpu<Msg: Clone>(
slot: &mut SubsurfaceSlot,
egl_ctx: &Arc<EglContext>,
gles_canvas: &mut Option<Canvas>,
compositor: &CompositorState,
view: &Element<Msg>,
pw: u32,
ph: u32,
scale: u32,
size_changed: bool,
)
{
if slot.egl_surface.is_none() || size_changed
{
match egl_ctx.create_surface( &slot.surface, pw as i32, ph as i32 )
{
Ok( es ) => slot.egl_surface = Some( es ),
Err( e ) =>
{
eprintln!( "ltk: subsurface EGL surface creation failed: {e}" );
return;
}
}
}
let es = slot.egl_surface.as_ref().unwrap();
if egl_ctx.make_current( es ).is_err() { return; }
let trace = RasterTrace::begin();
let canvas = gles_canvas.get_or_insert_with( ||
{
let mut c = Canvas::new_gles( Arc::clone( egl_ctx.gl() ), egl_ctx.version, pw, ph );
c.set_dpi_scale( scale as f32 );
if let Some( reg ) = crate::theme::build_font_registry()
{
c.set_font_registry( Arc::new( reg ) );
}
c
} );
if canvas.size() != ( pw, ph )
{
canvas.resize( pw, ph );
canvas.set_dpi_scale( scale as f32 );
}
paint_detached::<Msg>( canvas, view, &mut slot.scroll_canvases, &mut slot.viewport_canvases );
canvas.present();
if let Some( t ) = trace { t.end( egl_ctx.gl(), "subsurface", pw, ph ); }
let wl = &slot.surface;
wl.set_buffer_scale( scale as i32 );
apply_input_region( wl, compositor, Some( &[] ), scale );
let _ = egl_ctx.swap_buffers_with_damage( es, &[ ( 0, 0, pw as i32, ph as i32 ) ] );
}
#[ allow( clippy::too_many_arguments ) ]
fn render_slot_software<Msg: Clone>(
slot: &mut SubsurfaceSlot,
shm: &Shm,
compositor: &CompositorState,
view: &Element<Msg>,
pw: u32,
ph: u32,
scale: u32,
shm_format: wl_shm::Format,
swap_rb: bool,
size_changed: bool,
)
{
if slot.pool.is_none() || size_changed
{
match SlotPool::new( ( pw * ph * 4 ) as usize, shm )
{
Ok( p ) => slot.pool = Some( p ),
Err( _ ) => return,
}
}
let pool = slot.pool.as_mut().unwrap();
let stride = pw * 4;
let ( buffer, canvas_buf ) = match pool.create_buffer(
pw as i32, ph as i32, stride as i32, shm_format,
)
{
Ok( r ) => r,
Err( _ ) => return,
};
let canvas = slot.canvas.get_or_insert_with( || {
let mut c = Canvas::new( pw, ph );
c.set_dpi_scale( scale as f32 );
if let Some( reg ) = crate::theme::build_font_registry()
{
c.set_font_registry( Arc::new( reg ) );
}
c
} );
if canvas.size() != ( pw, ph )
{
canvas.resize( pw, ph );
canvas.set_dpi_scale( scale as f32 );
}
paint_detached::<Msg>( canvas, view, &mut slot.scroll_canvases, &mut slot.viewport_canvases );
canvas.write_to_wayland_buf( canvas_buf, swap_rb );
let wl = &slot.surface;
if buffer.attach_to( wl ).is_err() { return; }
wl.set_buffer_scale( scale as i32 );
wl.damage_buffer( 0, 0, pw as i32, ph as i32 );
apply_input_region( wl, compositor, Some( &[] ), scale );
wl.commit();
}