backend: consolidate rendering code

This commit is contained in:
Victoria Brekenfeld 2022-02-04 21:04:17 +01:00
parent 78cc1713ad
commit b0cf94047f
7 changed files with 236 additions and 171 deletions

View file

@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
#[cfg(feature = "debug")]
use crate::state::Fps;
use crate::{
backend::cursor,
backend::render,
state::{BackendData, Common, State},
utils::GlobalDrop,
};
@ -77,6 +79,8 @@ pub struct Surface {
pending: bool,
render_timer: TimerHandle<(dev_t, crtc::Handle)>,
render_timer_token: Option<RegistrationToken>,
#[cfg(feature = "debug")]
fps: Fps,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
@ -357,6 +361,8 @@ impl Device {
pending: true,
render_timer: timer_handle,
render_timer_token: Some(timer_token),
#[cfg(feature = "debug")]
fps: Fps::default(),
};
self.surfaces.insert(crtc, data);
@ -370,54 +376,28 @@ impl Surface {
renderer: &mut Gles2Renderer,
state: &mut Common,
) -> Result<()> {
#[allow(unused_mut)]
let mut custom_elements = Vec::new();
#[cfg(feature = "debug")]
{
let space = state.spaces.active_space(&self.output);
let size = space.output_geometry(&self.output).unwrap();
let scale = space.output_scale(&self.output).unwrap();
let frame = debug_ui(state, &self.fps, size, scale, true);
custom_elements.push(
Box::new(frame) as smithay::desktop::space::DynamicRenderElements<Gles2Renderer>
);
}
for seat in &state.seats {
if let Some(cursor) = cursor::draw_cursor(
renderer,
seat,
&state.start_time,
true,
) {
custom_elements.push(cursor)
}
}
let space = state.spaces.active_space_mut(&self.output);
let (buffer, age) = self
.surface
.next_buffer()
.with_context(|| "Failed to allocate buffer")?;
renderer
.bind(buffer)
.with_context(|| "Failed to bind buffer")?;
match space.render_output(
match render::render_output(
renderer,
age,
state,
&self.output,
age as usize,
[0.153, 0.161, 0.165, 1.0],
&*custom_elements,
false,
#[cfg(feature = "debug")]
&mut self.fps
) {
Ok(_) => {
self.surface
.queue_buffer()
.with_context(|| "Failed to submit buffer for display")?;
#[cfg(feature = "debug")]
{
self.fps.tick();
}
}
Err(err) => {
self.surface.reset_buffers();

View file

@ -4,7 +4,8 @@ use crate::state::State;
use anyhow::Result;
use smithay::reexports::calloop::EventLoop;
pub mod cursor;
pub mod render;
pub mod kms;
pub mod winit;
pub mod x11;

View file

@ -1,31 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::{
cell::RefCell,
io::Read,
rc::Rc,
sync::Mutex,
};
use crate::state::get_dnd_icon;
use smithay::{
backend::{
renderer::{Frame, ImportAll, Renderer, Texture, gles2},
renderer::{gles2, Frame, ImportAll, Renderer, Texture},
SwapBuffersError,
},
desktop::space::{RenderElement, SpaceOutputTuple, SurfaceTree, DynamicRenderElements},
desktop::space::{DynamicRenderElements, RenderElement, SpaceOutputTuple, SurfaceTree},
reexports::wayland_server::protocol::wl_surface,
utils::{Logical, Buffer, Point, Rectangle, Size, Transform},
utils::{Buffer, Logical, Point, Rectangle, Size, Transform},
wayland::{
compositor::{get_role, with_states},
seat::{Seat, CursorImageAttributes, CursorImageStatus},
seat::{CursorImageAttributes, CursorImageStatus, Seat},
},
};
use std::{cell::RefCell, io::Read, rc::Rc, sync::Mutex};
use xcursor::{
parser::{parse_xcursor, Image},
CursorTheme,
};
use crate::state::get_dnd_icon;
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../resources/cursor.rgba");
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba");
#[derive(Debug, Clone)]
pub struct Cursor {
@ -45,7 +40,9 @@ impl Cursor {
let theme = CursorTheme::load(&name);
let icons = load_icon(&theme)
.map_err(|err| slog_scope::warn!("Unable to load xcursor: {}, using fallback cursor", err))
.map_err(|err| {
slog_scope::warn!("Unable to load xcursor: {}, using fallback cursor", err)
})
.unwrap_or_else(|_| {
vec![Image {
size: 32,
@ -69,7 +66,9 @@ impl Cursor {
}
impl Default for Cursor {
fn default() -> Cursor { Cursor::load() }
fn default() -> Cursor {
Cursor::load()
}
}
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
@ -79,9 +78,9 @@ fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
.min_by_key(|image| (size as i32 - image.size as i32).abs())
.unwrap();
images
.iter()
.filter(move |image| image.width == nearest_image.width && image.height == nearest_image.height)
images.iter().filter(move |image| {
image.width == nearest_image.width && image.height == nearest_image.height
})
}
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
@ -142,7 +141,9 @@ where
position -= match ret {
Some(h) => h,
None => {
slog_scope::warn!("Trying to display as a cursor a surface that does not have the CursorImage role.");
slog_scope::warn!(
"Trying to display as a cursor a surface that does not have the CursorImage role."
);
(0, 0).into()
}
};
@ -160,7 +161,9 @@ where
T: Texture + 'static,
{
if get_role(&surface) != Some("dnd_icon") {
slog_scope::warn!("Trying to display as a dnd icon a surface that does not have the DndIcon role.");
slog_scope::warn!(
"Trying to display as a dnd icon a surface that does not have the DndIcon role."
);
}
SurfaceTree {
surface,
@ -176,7 +179,11 @@ pub struct PointerElement<T: Texture> {
}
impl<T: Texture> PointerElement<T> {
pub fn new(texture: T, relative_pointer_pos: Point<i32, Logical>, new_frame: bool) -> PointerElement<T> {
pub fn new(
texture: T,
relative_pointer_pos: Point<i32, Logical>,
new_frame: bool,
) -> PointerElement<T> {
let size = texture.size().to_logical(1, Transform::Normal);
PointerElement {
texture,
@ -202,8 +209,15 @@ where
Rectangle::from_loc_and_size(self.position, self.size)
}
fn accumulated_damage(&self, _: Option<SpaceOutputTuple<'_, '_>>) -> Vec<Rectangle<i32, Logical>> {
if self.new_frame { vec![Rectangle::from_loc_and_size((0, 0), self.size)] } else { vec![] }
fn accumulated_damage(
&self,
_: Option<SpaceOutputTuple<'_, '_>>,
) -> Vec<Rectangle<i32, Logical>> {
if self.new_frame {
vec![Rectangle::from_loc_and_size((0, 0), self.size)]
} else {
vec![]
}
}
fn draw(
@ -242,24 +256,15 @@ pub type Textures = Vec<(Image, gles2::Gles2Texture)>;
pub fn draw_cursor(
renderer: &mut gles2::Gles2Renderer,
seat: &Seat,
location: Point<i32, Logical>,
start_time: &std::time::Instant,
draw_default: bool,
) -> Option<DynamicRenderElements<gles2::Gles2Renderer>>
{
let pointer = match seat.get_pointer() {
Some(ptr) => ptr,
None => return None,
};
let location = pointer.current_location();
) -> Option<DynamicRenderElements<gles2::Gles2Renderer>> {
// draw the dnd icon if applicable
{
if let Some(wl_surface) = get_dnd_icon(seat) {
if wl_surface.as_ref().is_alive() {
return Some(Box::new(draw_dnd_icon(
wl_surface,
location.to_i32_round(),
)));
return Some(Box::new(draw_dnd_icon(wl_surface, location)));
}
}
}
@ -284,13 +289,15 @@ pub fn draw_cursor(
if let CursorImageStatus::Image(wl_surface) = cursor_status {
Some(Box::new(draw_surface_cursor(
wl_surface.clone(),
location.to_i32_round(),
location,
)))
} else if draw_default {
let seat_userdata = seat.user_data();
seat_userdata.insert_if_missing(CursorState::default);
let state = seat_userdata.get::<CursorState>().unwrap();
let frame = state.cursor.get_image(1, start_time.elapsed().as_millis() as u32);
let frame = state
.cursor
.get_image(1, start_time.elapsed().as_millis() as u32);
let new_frame = state.current_image.borrow().as_ref() != Some(&frame);
let egl_userdata = renderer.egl_context().user_data();
@ -302,8 +309,13 @@ pub fn draw_cursor(
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None })
.cloned()
.unwrap_or_else(|| {
let texture = import_bitmap(renderer, &frame.pixels_rgba, gles2::ffi::RGBA, (frame.width as i32, frame.height as i32))
.expect("Failed to import cursor bitmap");
let texture = import_bitmap(
renderer,
&frame.pixels_rgba,
gles2::ffi::RGBA,
(frame.width as i32, frame.height as i32),
)
.expect("Failed to import cursor bitmap");
pointer_images_ref.push((frame.clone(), texture.clone()));
texture
});
@ -312,7 +324,7 @@ pub fn draw_cursor(
Some(Box::new(PointerElement::new(
pointer_image.clone(),
location.to_i32_round() - hotspot,
location - hotspot,
new_frame,
)))
} else {
@ -334,8 +346,16 @@ pub fn import_bitmap(
let mut tex = 0;
gl.GenTextures(1, &mut tex);
gl.BindTexture(ffi::TEXTURE_2D, tex);
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_S, ffi::CLAMP_TO_EDGE as i32);
gl.TexParameteri(ffi::TEXTURE_2D, ffi::TEXTURE_WRAP_T, ffi::CLAMP_TO_EDGE as i32);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_WRAP_S,
ffi::CLAMP_TO_EDGE as i32,
);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_WRAP_T,
ffi::CLAMP_TO_EDGE as i32,
);
gl.TexImage2D(
ffi::TEXTURE_2D,
0,
@ -349,10 +369,6 @@ pub fn import_bitmap(
);
gl.BindTexture(ffi::TEXTURE_2D, 0);
gles2::Gles2Texture::from_raw(
renderer,
tex,
size,
)
gles2::Gles2Texture::from_raw(renderer, tex, size)
})
}
}

81
src/backend/render/mod.rs Normal file
View file

@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::state::Common;
#[cfg(feature = "debug")]
use crate::{
debug::{debug_ui, fps_ui},
state::Fps,
};
use smithay::{
backend::renderer::gles2::Gles2Renderer,
desktop::space::{DynamicRenderElements, RenderError},
utils::{Logical, Rectangle},
wayland::output::Output,
};
mod cursor;
pub fn render_output(
renderer: &mut Gles2Renderer,
age: u8,
state: &mut Common,
output: &Output,
hardware_cursor: bool,
#[cfg(feature = "debug")] fps: &mut Fps,
) -> Result<Option<Vec<Rectangle<i32, Logical>>>, RenderError<Gles2Renderer>> {
#[cfg(feature = "debug")]
{
fps.start();
}
#[allow(unused_mut)]
let mut custom_elements = Vec::<DynamicRenderElements<Gles2Renderer>>::new();
#[cfg(feature = "debug")]
{
let space = state.spaces.active_space(output);
let output_geo = space.output_geometry(output)
.unwrap_or(Rectangle::from_loc_and_size((0, 0), (0, 0)));
let scale = space.output_scale(output).unwrap();
let fps_overlay = fps_ui(state, fps, output_geo, scale);
custom_elements.push(Box::new(fps_overlay));
let mut area = state.spaces.global_space();
area.loc = state.spaces.space_relative_output_geometry((0, 0), output);
//let output_geo = state.spaces.output_geometry(output);
if let Some(debug_overlay) = debug_ui(state, area, scale) {
custom_elements.push(Box::new(debug_overlay));
}
}
for seat in &state.seats {
let pointer = match seat.get_pointer() {
Some(ptr) => ptr,
None => continue,
};
let location = state.spaces.space_relative_output_geometry(pointer.current_location().to_i32_round(), output);
if let Some(cursor) =
cursor::draw_cursor(renderer, seat, location, &state.start_time, !hardware_cursor)
{
custom_elements.push(cursor)
}
}
let res = state.spaces.active_space_mut(output).render_output(
renderer,
&output,
age as usize,
[0.153, 0.161, 0.165, 1.0],
&*custom_elements,
);
#[cfg(feature = "debug")]
{
fps.end();
}
res
}

View file

@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
backend::cursor,
backend::render,
input::{set_active_output, Devices},
state::{BackendData, State, Common},
state::{BackendData, Common, State},
};
use anyhow::{Context, Result};
use smithay::{
@ -27,9 +27,7 @@ use smithay::{
use std::{cell::RefCell, rc::Rc};
#[cfg(feature = "debug")]
use crate::{debug::debug_ui, state::Fps};
#[cfg(feature = "debug")]
use smithay::backend::renderer::gles2::Gles2Renderer;
use crate::state::Fps;
pub struct WinitState {
// The winit backend currently has no notion of multiple windows
@ -42,52 +40,27 @@ pub struct WinitState {
impl WinitState {
pub fn render_output(&mut self, state: &mut Common) -> Result<()> {
#[allow(unused_mut)]
let mut custom_elements = Vec::new();
#[cfg(feature = "debug")]
{
let space = state.spaces.active_space(&self.output);
let size = space.output_geometry(&self.output).unwrap();
let scale = space.output_scale(&self.output).unwrap();
let frame = debug_ui(state, &self.fps, size, scale, true);
custom_elements.push(
Box::new(frame) as smithay::desktop::space::DynamicRenderElements<Gles2Renderer>
);
}
let space = state.spaces.active_space_mut(&self.output);
let mut backend = self.backend.borrow_mut();
for seat in &state.seats {
if let Some(cursor) = cursor::draw_cursor(
backend.renderer(),
seat,
&state.start_time,
false,
) {
custom_elements.push(cursor)
}
}
let backend = &mut *self.backend.borrow_mut();
backend.bind().with_context(|| "Failed to bind buffer")?;
let age = backend.buffer_age().unwrap_or(0);
match space.render_output(
match render::render_output(
backend.renderer(),
age as u8,
state,
&self.output,
age as usize,
[0.153, 0.161, 0.165, 1.0],
&*custom_elements,
true,
#[cfg(feature = "debug")]
&mut self.fps,
) {
Ok(damage) => {
space.send_frames(false, state.start_time.elapsed().as_millis() as u32);
state
.spaces
.active_space_mut(&self.output)
.send_frames(true, state.start_time.elapsed().as_millis() as u32);
backend
.submit(damage.as_ref().map(|x| &**x), 1.0)
.with_context(|| "Failed to submit buffer for display")?;
#[cfg(feature = "debug")]
{
self.fps.tick();
}
}
Err(err) => {
anyhow::bail!("Rendering failed: {}", err);

View file

@ -1,9 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::{
backend::cursor,
backend::render,
input::{set_active_output, Devices},
state::{BackendData, State, Common},
state::{BackendData, Common, State},
utils::GlobalDrop,
};
use anyhow::{Context, Result};
@ -37,7 +37,7 @@ use std::{
};
#[cfg(feature = "debug")]
use crate::{debug::debug_ui, state::Fps};
use crate::state::Fps;
pub struct X11State {
allocator: Arc<Mutex<GbmDevice<DrmNode>>>,
@ -104,7 +104,8 @@ impl X11State {
{
slog_scope::error!("Error rendering: {}", err);
}
surface.pending = false;
surface.dirty = false;
surface.pending = true;
}
})
.with_context(|| "Failed to add output to event loop")?;
@ -114,6 +115,7 @@ impl X11State {
surface,
output: output.clone(),
render: ping.clone(),
dirty: false,
pending: true,
#[cfg(feature = "debug")]
fps: Fps::default(),
@ -127,8 +129,8 @@ impl X11State {
pub fn schedule_render(&mut self, output: &Output) {
if let Some(surface) = self.surfaces.iter_mut().find(|s| s.output == *output) {
surface.dirty = true;
if !surface.pending {
surface.pending = true;
surface.render.ping();
}
}
@ -140,6 +142,7 @@ pub struct Surface {
surface: X11Surface,
output: Output,
render: ping::Ping,
dirty: bool,
pending: bool,
#[cfg(feature = "debug")]
fps: Fps,
@ -152,32 +155,6 @@ impl Surface {
renderer: &mut Gles2Renderer,
state: &mut Common,
) -> Result<()> {
#[allow(unused_mut)]
let mut custom_elements = Vec::new();
#[cfg(feature = "debug")]
{
let space = state.spaces.active_space(&self.output);
let size = space.output_geometry(&self.output).unwrap();
let scale = space.output_scale(&self.output).unwrap();
let frame = debug_ui(state, &self.fps, size, scale, true);
custom_elements.push(
Box::new(frame) as smithay::desktop::space::DynamicRenderElements<Gles2Renderer>
);
}
for seat in &state.seats {
if let Some(cursor) = cursor::draw_cursor(
renderer,
seat,
&state.start_time,
false,
) {
custom_elements.push(cursor)
}
}
let space = state.spaces.active_space_mut(&self.output);
let (buffer, age) = self
.surface
.buffer()
@ -185,22 +162,24 @@ impl Surface {
renderer
.bind(buffer)
.with_context(|| "Failed to bind buffer")?;
match space.render_output(
match render::render_output(
renderer,
age as u8,
state,
&self.output,
age as usize,
[0.153, 0.161, 0.165, 1.0],
&*custom_elements,
true,
#[cfg(feature = "debug")]
&mut self.fps,
) {
Ok(_) => {
space.send_frames(false, state.start_time.elapsed().as_millis() as u32);
state
.spaces
.active_space_mut(&self.output)
.send_frames(true, state.start_time.elapsed().as_millis() as u32);
self.surface
.submit()
.with_context(|| "Failed to submit buffer for display")?;
#[cfg(feature = "debug")]
{
self.fps.tick();
}
}
Err(err) => {
self.surface.reset_buffers();
@ -287,18 +266,33 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
.iter_mut()
.find(|s| s.window.id() == window_id)
{
surface.render.ping();
let output = &surface.output;
output.delete_mode(output.current_mode().unwrap());
output.change_current_state(Some(mode), None, None, None);
output.set_preferred(mode);
layer_map_for_output(output).arrange();
surface.dirty = true;
if !surface.pending {
surface.render.ping();
}
}
}
},
X11Event::Refresh { window_id } | X11Event::PresentCompleted { window_id } => {
if let Some(surface) = state
.backend
.x11()
.surfaces
.iter_mut()
.find(|s| s.window.id() == window_id)
{
if surface.dirty {
surface.render.ping();
} else {
surface.pending = false;
}
}
},
X11Event::Input(event) => state.process_x11_event(event),
_ => {},
})
.map_err(|_| anyhow::anyhow!("Failed to insert X11 Backend into event loop"))?;

View file

@ -3,7 +3,7 @@
pub use smithay::{
desktop::Space,
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{Logical, Rectangle, Size},
utils::{Logical, Point, Rectangle, Size},
wayland::output::Output,
};
use std::{cell::Cell, mem::MaybeUninit};
@ -126,6 +126,26 @@ impl Workspaces {
.size
}
pub fn global_space(&self) -> Rectangle<i32, Logical> {
let size = self.outputs.iter().fold((0, 0), |(w, h), output| {
let size = self.output_size(output);
(w + size.w, std::cmp::max(h, size.h))
});
Rectangle::from_loc_and_size((0, 0), size)
}
pub fn space_relative_output_geometry(
&self,
global_loc: impl Into<Point<i32, Logical>>,
output: &Output,
) -> Point<i32, Logical> {
match self.mode {
Mode::Global { .. } => global_loc.into(),
Mode::OutputBound => global_loc.into() - self.output_geometry(output).loc,
}
}
pub fn output_geometry(&self, output: &Output) -> Rectangle<i32, Logical> {
// due to our different modes, we cannot just ask the space for the global output coordinates,
// because for `Mode::OutputBound` the origin will always be (0, 0)