drm/compositor: Use pixman for cursor

This allows the removal of Offscreen and ExportMem trait usage. Aside
from unnecessary GL for small/simple cursor, it also should help with
WLCS for virtual devices (like VKMS/kmsro)
This commit is contained in:
Ben Widawsky 2023-10-21 14:00:36 -07:00 committed by Victoria Brekenfeld
parent 0507c89955
commit 5857a843c5
3 changed files with 137 additions and 80 deletions

View file

@ -82,7 +82,7 @@ pkg-config = { version = "0.3.17", optional = true }
cc = { version = "1.0.79", optional = true }
[features]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_libseat", "backend_x11", "backend_winit", "desktop", "renderer_gl", "renderer_multi", "xwayland", "wayland_frontend", "backend_vulkan"]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_libseat", "backend_x11", "backend_winit", "desktop", "renderer_gl", "renderer_pixman", "renderer_multi", "xwayland", "wayland_frontend", "backend_vulkan"]
backend_winit = ["winit", "backend_egl", "wayland-egl", "renderer_gl"]
backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"]
backend_drm = ["drm", "drm-ffi"]

View file

@ -51,6 +51,7 @@ udev = [
"smithay/backend_session_libseat",
"image",
"smithay/renderer_gl",
"smithay/renderer_pixman",
"smithay/renderer_multi",
"xcursor",
]

View file

@ -9,8 +9,7 @@
//! will be rendered on the primary plane using the provided [`Renderer`].
//! Additionally it will try to assign the top most element that fit's into the cursor size (as specified
//! by the [`DrmDevice`](crate::backend::drm::DrmDevice)) on the cursor plane. If the element can not be
//! directly scanned out the renderer will be used to render the element into an [`Offscreen`] buffer that
//! is copied over to the allocated gbm buffer for the cursor plane.
//! directly scanned out, pixman will be used to render the element.
//!
//! Note: While the [`DrmCompositor`] also works on *legacy* drm the use of overlay and cursor planes is disabled in that case.
//! Direct scan-out will only work with an atomic [`DrmSurface`].
@ -105,7 +104,7 @@
//!
//! # let elements: Vec<WaylandSurfaceRenderElement<GlesRenderer>> = Vec::new();
//! let render_frame_result = compositor
//! .render_frame::<_, _, GlesTexture>(&mut renderer, &elements, CLEAR_COLOR)
//! .render_frame::<_, _>(&mut renderer, &elements, CLEAR_COLOR)
//! .expect("failed to render frame");
//!
//! if render_frame_result.damage.is_some() {
@ -141,6 +140,11 @@ use smallvec::SmallVec;
use tracing::{debug, error, info, info_span, instrument, trace, warn};
use wayland_server::{protocol::wl_buffer::WlBuffer, Resource};
#[cfg(feature = "renderer_pixman")]
use crate::backend::renderer::{
pixman::{PixmanError, PixmanRenderBuffer, PixmanRenderer, PixmanTexture},
ImportAll, Unbind,
};
use crate::{
backend::{
allocator::{
@ -159,7 +163,7 @@ use crate::{
},
sync::SyncPoint,
utils::{CommitCounter, DamageBag, DamageSnapshot},
Bind, Blit, DebugFlags, ExportMem, Frame as RendererFrame, Offscreen, Renderer, Texture,
Bind, Blit, DebugFlags, Frame as RendererFrame, Renderer, Texture,
},
SwapBuffersError,
},
@ -1289,6 +1293,8 @@ struct CursorState<G: AsFd + 'static> {
framebuffer_exporter: GbmDevice<G>,
previous_output_transform: Option<Transform>,
previous_output_scale: Option<Scale<f64>>,
#[cfg(feature = "renderer_pixman")]
pixman_renderer: Option<PixmanRenderer>,
}
#[derive(Debug, thiserror::Error, Copy, Clone)]
@ -1522,6 +1528,15 @@ where
) {
Ok((swapchain, current_frame, is_opaque)) => {
let cursor_state = gbm.map(|gbm| {
#[cfg(feature = "renderer_pixman")]
let pixman_renderer = match PixmanRenderer::new() {
Ok(pixman_renderer) => Some(pixman_renderer),
Err(err) => {
tracing::warn!(?err, "failed to initialize pixman renderer for cursor plane");
None
}
};
let cursor_allocator = GbmAllocator::new(
gbm.clone(),
BufferObjectFlags::CURSOR | BufferObjectFlags::WRITE,
@ -1531,6 +1546,8 @@ where
framebuffer_exporter: gbm,
previous_output_scale: None,
previous_output_transform: None,
#[cfg(feature = "renderer_pixman")]
pixman_renderer,
}
});
@ -1755,7 +1772,7 @@ where
/// - `elements` for this frame in front-to-back order
#[instrument(level = "trace", parent = &self.span, skip_all)]
#[profiling::function]
pub fn render_frame<'a, R, E, Target>(
pub fn render_frame<'a, R, E>(
&mut self,
renderer: &mut R,
elements: &'a [E],
@ -1763,7 +1780,7 @@ where
) -> Result<RenderFrameResult<'a, A::Buffer, F::Framebuffer, E>, RenderFrameErrorType<A, F, R>>
where
E: RenderElement<R>,
R: Renderer + Bind<Dmabuf> + Offscreen<Target> + ExportMem,
R: Renderer + Bind<Dmabuf>,
<R as Renderer>::TextureId: Texture + 'static,
{
if !self.surface.is_active() {
@ -2646,7 +2663,7 @@ where
#[allow(clippy::too_many_arguments)]
#[instrument(level = "trace", skip_all)]
#[profiling::function]
fn try_assign_element<'a, R, E, Target>(
fn try_assign_element<'a, R, E>(
&mut self,
renderer: &mut R,
element: &'a E,
@ -2666,7 +2683,7 @@ where
try_assign_primary_plane: bool,
) -> Result<PlaneAssignment, Option<RenderingReason>>
where
R: Renderer + Bind<Dmabuf> + Offscreen<Target> + ExportMem,
R: Renderer + Bind<Dmabuf>,
E: RenderElement<R>,
{
// Check if we have a free plane, otherwise we can exit early
@ -2840,7 +2857,7 @@ where
#[allow(clippy::too_many_arguments)]
#[instrument(level = "trace", skip_all)]
#[profiling::function]
fn try_assign_cursor_plane<R, E, Target>(
fn try_assign_cursor_plane<R, E>(
&mut self,
renderer: &mut R,
element: &E,
@ -2853,7 +2870,7 @@ where
output_geometry: Rectangle<i32, Physical>,
) -> Option<PlaneAssignment>
where
R: Renderer + Offscreen<Target> + ExportMem,
R: Renderer,
E: RenderElement<R>,
{
// if we have no cursor plane we can exit early
@ -3024,6 +3041,21 @@ where
let cursor_buffer_size = self.cursor_size.to_logical(1).to_buffer(1, Transform::Normal);
#[cfg(not(feature = "renderer_pixman"))]
if !copy_element_to_cursor_bo(
renderer,
element,
element_size,
self.cursor_size,
output_transform,
&cursor_state.framebuffer_exporter,
&mut cursor_buffer,
) {
tracing::trace!("failed to copy element to cursor bo, skipping element on cursor plane");
return None;
}
#[cfg(feature = "renderer_pixman")]
if !copy_element_to_cursor_bo(
renderer,
element,
@ -3036,81 +3068,105 @@ where
profiling::scope!("render cursor plane");
tracing::trace!("cursor fast-path copy failed, falling back to rendering using offscreen buffer");
let offscreen_buffer = match renderer.create_buffer(DrmFourcc::Argb8888, cursor_buffer_size) {
Ok(buffer) => buffer,
let Some(storage) = element.underlying_storage(renderer) else {
trace!("Can't obtain cursor's underlying storage");
return None;
};
let Some(pixman_renderer) = cursor_state.pixman_renderer.as_mut() else {
return None;
};
// Create a pixman image from the source cursor data. This will either be set by the
// client, or the compositor's choice.
let cursor_texture = match storage {
UnderlyingStorage::Wayland(buffer) => pixman_renderer
.import_buffer(&buffer, None, &[element.src().to_i32_up()])
.transpose()
.ok()
.flatten(),
UnderlyingStorage::Memory(memory) => {
let format = memory.format();
let size = memory.size();
let Ok(pixman_format) = pixman::FormatCode::try_from(format) else {
debug!("No pixman format for {format}");
return None;
};
unsafe {
match pixman::Image::from_raw_mut(
pixman_format,
size.w as usize,
size.h as usize,
memory.as_ptr() as *mut u32,
memory.stride() as usize,
false,
) {
Ok(image) => Some(PixmanTexture::from(image)),
Err(e) => {
debug!("pixman cursor: {e}");
None
}
}
}
}
}?;
let ret = cursor_buffer
.map_mut::<_, _, Result<_, <PixmanRenderer as Renderer>::Error>>(
&cursor_state.framebuffer_exporter,
0,
0,
cursor_buffer_size.w as u32,
cursor_buffer_size.h as u32,
|mbo| {
let plane_pixman_format = pixman::FormatCode::try_from(DrmFourcc::Argb8888).unwrap();
let cursor_dst = unsafe {
pixman::Image::from_raw_mut(
plane_pixman_format,
mbo.width() as usize,
mbo.height() as usize,
mbo.buffer_mut().as_mut_ptr() as *mut u32,
mbo.stride() as usize,
false,
)
}
.map_err(|_| PixmanError::ImportFailed)?;
pixman_renderer.bind(PixmanRenderBuffer::from(cursor_dst))?;
let mut frame = pixman_renderer.render(self.cursor_size, output_transform)?;
frame.clear(
[0f32, 0f32, 0f32, 0f32],
&[Rectangle::from_loc_and_size((0, 0), self.cursor_size)],
)?;
let src = element.src();
let dst = Rectangle::from_loc_and_size((0, 0), element_geometry.size);
frame.render_texture_from_to(
&cursor_texture,
src,
dst,
&[dst],
element.transform(),
element.alpha(),
)?;
frame.finish()?.wait();
Ok(())
},
)
.expect("Lost track of cursor device");
_ = pixman_renderer.unbind();
match ret {
Err(err) => {
debug!(
"failed to create offscreen buffer for cursor {:?}: {}",
plane_info.handle, err
);
debug!("{err}");
return None;
}
};
if let Err(err) = renderer.bind(offscreen_buffer) {
debug!(
"failed to bind cursor buffer for cursor {:?}: {}",
plane_info.handle, err
);
return None;
};
// save the renderer debug flags and disable all for the cursor plane
let renderer_debug_flags = renderer.debug_flags();
renderer.set_debug_flags(DebugFlags::empty());
let mut render = || {
let mut frame = renderer.render(self.cursor_size, output_transform)?;
frame.clear(
[0f32, 0f32, 0f32, 0f32],
&[Rectangle::from_loc_and_size((0, 0), self.cursor_size)],
)?;
let src = element.src();
let dst = Rectangle::from_loc_and_size((0, 0), element_geometry.size);
element.draw(&mut frame, src, dst, &[dst])?;
frame.finish()?.wait();
Ok::<(), <R as Renderer>::Error>(())
};
let render_res = render();
// restore the renderer debug flags
renderer.set_debug_flags(renderer_debug_flags);
if let Err(err) = render_res {
debug!("failed to render cursor element: {}", err);
return None;
}
let copy_rect = Rectangle::from_loc_and_size((0, 0), cursor_buffer_size);
let mapping = match renderer.copy_framebuffer(copy_rect, DrmFourcc::Argb8888) {
Ok(mapping) => mapping,
Err(err) => {
info!("failed to export cursor offscreen buffer: {}", err);
Ok(Err(err)) => {
debug!("{err}");
return None;
}
Ok(Ok(_)) => (),
};
let data = match renderer.map_texture(&mapping) {
Ok(data) => data,
Err(err) => {
info!("failed to map exported cursor offscreen buffer: {}", err);
return None;
}
};
let Ok(res) = cursor_buffer.write(data) else {
info!("failed to write cursor buffer, device destroyed");
return None;
};
if let Err(err) = res {
info!("failed to write cursor buffer; {}", err);
return None;
}
};
let src = Rectangle::from_loc_and_size(Point::default(), cursor_buffer_size).to_f64();