use std::sync::Arc;
use smithay_client_toolkit::compositor::{ CompositorState, Region };
use smithay_client_toolkit::reexports::client::protocol::{ wl_shm, wl_surface::WlSurface };
use smithay_client_toolkit::shm::Shm;
use crate::app::App;
use crate::draw::fit_canvas;
use crate::egl_context::egl_damage;
use crate::event_loop::perf::RasterTrace;
use crate::platform::Presentation;
use crate::render::Canvas;
use crate::types::{ Color, Rect };
use super::{ WaylandPlatform, WaylandSurfaceState };
pub( crate ) fn pick_shm_format( shm: &Shm ) -> ( wl_shm::Format, bool )
{
if shm.formats().contains( &wl_shm::Format::Abgr8888 )
{
( wl_shm::Format::Abgr8888, false )
} else {
( wl_shm::Format::Argb8888, true )
}
}
pub( super ) fn prepare_canvas<A: App>
(
platform: &WaylandPlatform<A>,
surface: &mut WaylandSurfaceState,
canvas: &mut Option<Canvas>,
pw: u32,
ph: u32,
scale: f32,
) -> bool
{
if let ( Some( ctx ), Some( es ) ) = ( platform.egl_context.as_ref(), surface.egl_surface.as_ref() )
{
if ctx.make_current( es ).is_err() { return false; }
surface.raster_trace = RasterTrace::begin();
fit_canvas( canvas, pw, ph, scale, || Canvas::new_gles( Arc::clone( ctx.gl() ), ctx.version, pw, ph ) );
return true;
}
if surface.pool.is_none() { return false; }
fit_canvas( canvas, pw, ph, scale, || Canvas::new( pw, ph ) );
true
}
pub( super ) fn present<A: App>
(
platform: &WaylandPlatform<A>,
surface: &mut WaylandSurfaceState,
canvas: &mut Canvas,
frame: &Presentation<'_>,
) -> bool
{
let ( pw, ph ) = canvas.size();
let Some( ( ctx, es ) ) = platform.egl_context.as_ref().zip( surface.egl_surface.as_ref() ) else
{
return present_shm( platform, surface, canvas, frame );
};
canvas.present();
if let Some( t ) = surface.raster_trace.take()
{
t.end( ctx.gl(), if frame.damage.is_empty() { "full" } else { "partial" }, pw, ph );
}
describe( platform, surface.surface.wl_surface(), &mut surface.opaque_full, frame );
let _ = ctx.swap_buffers_with_damage( es, &egl_damage( frame.damage, pw, ph ) );
true
}
fn present_shm<A: App>
(
platform: &WaylandPlatform<A>,
surface: &mut WaylandSurfaceState,
canvas: &mut Canvas,
frame: &Presentation<'_>,
) -> bool
{
let ( pw, ph ) = canvas.size();
let ( format, swap_rb ) = pick_shm_format( &platform.shm );
let Some( pool ) = surface.pool.as_mut() else { return false };
let Ok( ( buffer, pixels ) ) = pool.create_buffer( pw as i32, ph as i32, ( pw * 4 ) as i32, format ) else { return false };
canvas.write_to_wayland_buf( pixels, swap_rb );
let wl_surface = surface.surface.wl_surface();
buffer.attach_to( wl_surface ).expect( "attach" );
if frame.damage.is_empty()
{
wl_surface.damage_buffer( 0, 0, pw as i32, ph as i32 );
} else {
for r in frame.damage
{
wl_surface.damage_buffer( r.x as i32, r.y as i32, r.width as i32, r.height as i32 );
}
}
describe( platform, wl_surface, &mut surface.opaque_full, frame );
wl_surface.commit();
true
}
fn describe<A: App>( platform: &WaylandPlatform<A>, wl_surface: &WlSurface, opaque_applied: &mut Option<Vec<Rect>>, frame: &Presentation<'_> )
{
let scale = frame.scale.round().max( 1.0 ) as u32;
sync_opaque_region( wl_surface, &platform.compositor_state, opaque_applied, frame.bg, frame.opaque_region, frame.width, frame.height, scale );
apply_input_region( wl_surface, &platform.compositor_state, frame.input_region, scale );
if let Some( focus ) = frame.frame
{
let _ = wl_surface.frame( &platform.qh, focus );
}
}
fn opaque_rects( bg: Color, region: Option<&[Rect]>, width: u32, height: u32, scale: u32 ) -> Vec<Rect>
{
let s = scale.max( 1 ) as f32;
if bg.a >= 1.0
{
vec![ Rect { x: 0.0, y: 0.0, width: width as f32 * s, height: height as f32 * s } ]
} else {
region.map( |r| r.to_vec() ).unwrap_or_default()
}
}
pub( crate ) fn sync_opaque_region(
wl_surface: &WlSurface,
compositor: &CompositorState,
applied: &mut Option<Vec<Rect>>,
bg: Color,
region: Option<&[Rect]>,
width: u32,
height: u32,
scale: u32,
)
{
let rects = opaque_rects( bg, region, width, height, scale );
if applied.as_ref() == Some( &rects ) { return; }
if rects.is_empty()
{
wl_surface.set_opaque_region( None );
} else if let Ok( wl_region ) = Region::new( compositor ) {
let s = scale.max( 1 ) as f32;
for r in &rects
{
wl_region.add(
( r.x / s ).round() as i32,
( r.y / s ).round() as i32,
( r.width / s ).round() as i32,
( r.height / s ).round() as i32,
);
}
wl_surface.set_opaque_region( Some( wl_region.wl_region() ) );
}
*applied = Some( rects );
}
pub( crate ) fn apply_input_region(
wl_surface: &WlSurface,
compositor: &CompositorState,
input_region: Option<&[Rect]>,
scale: u32,
)
{
if let Some( regions ) = input_region
{
if let Ok( region ) = Region::new( compositor )
{
let s = scale.max( 1 ) as f32;
for r in regions
{
region.add(
( r.x / s ).round() as i32,
( r.y / s ).round() as i32,
( r.width / s ).round() as i32,
( r.height / s ).round() as i32,
);
}
wl_surface.set_input_region( Some( region.wl_region() ) );
}
} else {
wl_surface.set_input_region( None );
}
}
#[ cfg( test ) ]
mod tests
{
use super::opaque_rects;
use crate::types::{ Color, Rect };
fn rect( x: f32, y: f32, width: f32, height: f32 ) -> Rect
{
Rect { x, y, width, height }
}
#[ test ]
fn an_opaque_background_covers_the_physical_surface()
{
let region = [ rect( 0.0, 0.0, 1.0, 1.0 ) ];
assert_eq!( opaque_rects( Color::rgb( 0.0, 0.0, 0.0 ), Some( ®ion ), 400, 300, 2 ), vec![ rect( 0.0, 0.0, 800.0, 600.0 ) ] );
}
#[ test ]
fn a_translucent_background_takes_the_app_region()
{
let region = [ rect( 0.0, 0.0, 100.0, 50.0 ) ];
assert_eq!( opaque_rects( Color::rgba( 0.0, 0.0, 0.0, 0.5 ), Some( ®ion ), 400, 300, 1 ), region.to_vec() );
assert!( opaque_rects( Color::rgba( 0.0, 0.0, 0.0, 0.0 ), None, 400, 300, 1 ).is_empty() );
}
}