renderer: Split Renderer trait into Import sub-traits

This commit is contained in:
Victor Brekenfeld 2021-06-05 19:58:51 +02:00 committed by Victor Berger
parent 2661b86019
commit 969cdda85c
10 changed files with 207 additions and 141 deletions

View File

@ -48,7 +48,7 @@ pkg-config = { version = "0.3.17", optional = true }
[features]
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_logind", "backend_winit", "renderer_gl", "xwayland", "wayland_frontend", "slog-stdlog"]
backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl", "use_system_lib"]
backend_winit = ["winit", "wayland-server/dlopen", "backend_egl", "wayland-egl", "renderer_gl"]
backend_drm = ["drm", "drm-ffi"]
backend_gbm = ["gbm"]
backend_egl = ["gl_generator"]

View File

@ -5,8 +5,7 @@ use std::cell::RefCell;
use slog::Logger;
use smithay::{
backend::{
egl::display::EGLBufferReader,
renderer::{Frame, Renderer, Texture, Transform},
renderer::{Frame, Renderer, Texture, Transform, ImportShm, BufferType, buffer_type},
SwapBuffersError,
},
reexports::wayland_server::protocol::{wl_buffer, wl_surface},
@ -17,6 +16,16 @@ use smithay::{
seat::CursorImageRole,
},
};
#[cfg(feature = "egl")]
use smithay::backend::{
egl::display::EGLBufferReader,
renderer::ImportEgl
};
// hacky...
#[cfg(not(feature = "egl"))]
pub trait ImportEgl {}
#[cfg(not(feature = "egl"))]
impl<T> ImportEgl for T {}
use crate::shell::{MyCompositorToken, MyWindowMap, SurfaceData};
@ -37,13 +46,14 @@ pub fn draw_cursor<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
surface: &wl_surface::WlSurface,
#[cfg(feature = "egl")]
egl_buffer_reader: Option<&EGLBufferReader>,
(x, y): (i32, i32),
token: MyCompositorToken,
log: &Logger,
) -> Result<(), SwapBuffersError>
where
R: Renderer<Error = E, TextureId = T, Frame = F>,
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportShm + ImportEgl,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static,
@ -62,6 +72,7 @@ where
renderer,
frame,
surface,
#[cfg(feature = "egl")]
egl_buffer_reader,
(x - dx, y - dy),
token,
@ -73,13 +84,14 @@ fn draw_surface_tree<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
root: &wl_surface::WlSurface,
#[cfg(feature = "egl")]
egl_buffer_reader: Option<&EGLBufferReader>,
location: (i32, i32),
compositor_token: MyCompositorToken,
log: &Logger,
) -> Result<(), SwapBuffersError>
where
R: Renderer<Error = E, TextureId = T, Frame = F>,
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportShm + ImportEgl,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static,
@ -95,6 +107,12 @@ where
let mut data = data.borrow_mut();
if data.texture.is_none() {
if let Some(buffer) = data.current_state.buffer.take() {
let texture = match buffer_type(
&buffer,
#[cfg(feature = "egl")]
egl_buffer_reader
) {
Some(BufferType::Shm) => {
let damage = attributes
.damage
.iter()
@ -104,28 +122,30 @@ where
Damage::Surface(rect) => rect.scale(attributes.buffer_scale),
})
.collect::<Vec<_>>();
match renderer.import_buffer(&buffer, Some(&attributes), &damage, egl_buffer_reader) {
Ok(m) => {
let buffer = if smithay::wayland::shm::with_buffer_contents(
&buffer,
|_, _| (),
)
.is_ok()
{
let result = renderer.import_shm_buffer(&buffer, Some(&attributes), &damage);
if result.is_ok() {
buffer.release();
}
Some(result)
},
#[cfg(feature = "egl")]
Some(BufferType::Egl) => Some(renderer.import_egl_buffer(&buffer, egl_buffer_reader.unwrap())),
_ => {
error!(log, "Unknown buffer format for: {:?}", buffer);
None
} else {
Some(buffer)
}
};
data.texture = Some(Box::new(BufferTextures { buffer, texture: m })
match texture {
Some(Ok(m)) => {
data.texture = Some(Box::new(BufferTextures { buffer: Some(buffer), texture: m })
as Box<dyn std::any::Any + 'static>)
}
// there was an error reading the buffer, release it, we
// already logged the error
Err(err) => {
// there was an error reading the buffer, release it.
Some(Err(err)) => {
warn!(log, "Error loading buffer: {:?}", err);
buffer.release();
}
},
None => buffer.release(),
};
}
}
@ -181,6 +201,7 @@ where
pub fn draw_windows<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
#[cfg(feature = "egl")]
egl_buffer_reader: Option<&EGLBufferReader>,
window_map: &MyWindowMap,
output_rect: Option<Rectangle>,
@ -188,7 +209,7 @@ pub fn draw_windows<R, E, F, T>(
log: &::slog::Logger,
) -> Result<(), SwapBuffersError>
where
R: Renderer<Error = E, TextureId = T, Frame = F>,
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportShm + ImportEgl,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static,
@ -210,6 +231,7 @@ where
renderer,
frame,
&wl_surface,
#[cfg(feature = "egl")]
egl_buffer_reader,
initial_place,
compositor_token,
@ -227,13 +249,14 @@ pub fn draw_dnd_icon<R, E, F, T>(
renderer: &mut R,
frame: &mut F,
surface: &wl_surface::WlSurface,
#[cfg(feature = "egl")]
egl_buffer_reader: Option<&EGLBufferReader>,
(x, y): (i32, i32),
token: MyCompositorToken,
log: &::slog::Logger,
) -> Result<(), SwapBuffersError>
where
R: Renderer<Error = E, TextureId = T, Frame = F>,
R: Renderer<Error = E, TextureId = T, Frame = F> + ImportShm + ImportEgl,
F: Frame<Error = E, TextureId = T>,
E: std::error::Error + Into<SwapBuffersError>,
T: Texture + 'static,
@ -244,5 +267,14 @@ where
"Trying to display as a dnd icon a surface that does not have the DndIcon role."
);
}
draw_surface_tree(renderer, frame, surface, egl_buffer_reader, (x, y), token, log)
draw_surface_tree(
renderer,
frame,
surface,
#[cfg(feature = "egl")]
egl_buffer_reader,
(x, y),
token,
log
)
}

View File

@ -1,6 +1,10 @@
use std::{process::Command, sync::atomic::Ordering};
use crate::{udev::UdevData, winit::WinitData, AnvilState};
use crate::AnvilState;
#[cfg(feature = "udev")]
use crate::udev::UdevData;
#[cfg(feature = "winit")]
use crate::winit::WinitData;
use smithay::{
backend::input::{

View File

@ -839,7 +839,7 @@ fn surface_commit(
}
#[cfg(not(feature = "egl"))]
{
next_state.dimensions = buffer_dimensions(&buffer, None);
next_state.dimensions = buffer_dimensions(&buffer);
}
next_state.buffer = Some(buffer);
}

View File

@ -84,10 +84,7 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
init_shm_global(&mut (*display).borrow_mut(), vec![], log.clone());
#[cfg(feature = "egl")]
let shell_handles = init_shell::<BackendData>(&mut display.borrow_mut(), log.clone());
#[cfg(not(feature = "egl"))]
let shell_handles = init_shell(&mut display.borrow_mut(), log.clone());
let socket_name = display
.borrow_mut()

View File

@ -110,6 +110,7 @@ pub fn run_udev(
* Initialize the compositor
*/
let pointer_bytes = include_bytes!("../resources/cursor2.rgba");
#[cfg(feature = "egl")]
let primary_gpu = primary_gpu(&session.seat()).unwrap_or_default();
// setup the timer
@ -118,6 +119,7 @@ pub fn run_udev(
let data = UdevData {
session,
output_map: Vec::new(),
#[cfg(feature = "egl")]
primary_gpu,
backends: HashMap::new(),
signaler: session_signal.clone(),
@ -669,9 +671,6 @@ fn render_surface(
cursor_status: &mut CursorImageStatus,
logger: &slog::Logger,
) -> Result<(), SwapBuffersError> {
#[cfg(not(feature = "egl"))]
let egl_buffer_reader = None;
surface.frame_submitted()?;
// get output coordinates
@ -693,6 +692,7 @@ fn render_surface(
draw_windows(
renderer,
frame,
#[cfg(feature = "egl")]
egl_buffer_reader,
window_map,
Some(Rectangle {
@ -720,6 +720,7 @@ fn render_surface(
renderer,
frame,
wl_surface,
#[cfg(feature = "egl")]
egl_buffer_reader,
(ptr_x, ptr_y),
*compositor_token,
@ -744,6 +745,7 @@ fn render_surface(
renderer,
frame,
wl_surface,
#[cfg(feature = "egl")]
egl_buffer_reader,
(ptr_x, ptr_y),
*compositor_token,

View File

@ -37,9 +37,6 @@ pub fn run_winit(
#[cfg(feature = "egl")]
let reader = renderer.borrow().bind_wl_display(&display.borrow()).ok();
#[cfg(not(feature = "egl"))]
let reader = None;
#[cfg(feature = "egl")]
if reader.is_some() {
info!(log, "EGL hardware-acceleration enabled");
@ -114,6 +111,7 @@ pub fn run_winit(
draw_windows(
renderer,
frame,
#[cfg(feature = "egl")]
state.egl_reader.as_ref(),
&*state.window_map.borrow(),
None,
@ -131,6 +129,7 @@ pub fn run_winit(
renderer,
frame,
surface,
#[cfg(feature = "egl")]
state.egl_reader.as_ref(),
(x as i32, y as i32),
state.ctoken,
@ -158,6 +157,7 @@ pub fn run_winit(
renderer,
frame,
surface,
#[cfg(feature = "egl")]
state.egl_reader.as_ref(),
(x as i32, y as i32),
state.ctoken,

View File

@ -1,7 +1,5 @@
//! Implementation of the rendering traits using OpenGL ES 2
#[cfg(feature = "wayland_frontend")]
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::ffi::CStr;
@ -32,12 +30,17 @@ use crate::backend::SwapBuffersError;
#[cfg(feature = "wayland_frontend")]
use crate::{
backend::egl::display::EGLBufferReader, utils::Rectangle, wayland::compositor::SurfaceAttributes,
utils::Rectangle,
wayland::compositor::SurfaceAttributes,
};
#[cfg(feature = "wayland_frontend")]
use wayland_commons::user_data::UserDataMap;
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
use crate::backend::egl::display::EGLBufferReader;
#[cfg(feature = "wayland_frontend")]
use wayland_server::protocol::{wl_buffer, wl_shm};
#[cfg(feature = "wayland_frontend")]
use super::ImportShm;
#[cfg(all(feature = "wayland_frontend", feature = "use_system_lib"))]
use super::ImportEgl;
#[allow(clippy::all, missing_docs)]
pub mod ffi {
@ -221,10 +224,6 @@ pub enum Gles2Error {
#[error("Error accessing the buffer ({0:?})")]
#[cfg(feature = "wayland_frontend")]
EGLBufferAccessError(crate::backend::egl::BufferAccessError),
/// The buffer backend is unknown or unsupported
#[error("Error accessing the buffer")]
#[cfg(feature = "wayland_frontend")]
UnknownBufferType,
/// This rendering operation was called without a previous `begin`-call
#[error("Call begin before doing any rendering operations")]
UnconstraintRenderingOperation,
@ -243,7 +242,6 @@ impl From<Gles2Error> for SwapBuffersError {
x @ Gles2Error::FramebufferBindingError
| x @ Gles2Error::BindBufferEGLError(_)
| x @ Gles2Error::UnsupportedPixelFormat(_)
| x @ Gles2Error::UnknownBufferType
| x @ Gles2Error::BufferAccessError(_)
| x @ Gles2Error::EGLBufferAccessError(_) => SwapBuffersError::TemporaryFailure(Box::new(x)),
}
@ -500,9 +498,9 @@ impl Gles2Renderer {
}
}
impl Gles2Renderer {
#[cfg(feature = "wayland_frontend")]
fn import_shm(
impl ImportShm for Gles2Renderer {
fn import_shm_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
@ -613,7 +611,19 @@ impl Gles2Renderer {
}
#[cfg(feature = "wayland_frontend")]
fn import_egl(
fn shm_formats(&self) -> &[wl_shm::Format] {
&[
wl_shm::Format::Abgr8888,
wl_shm::Format::Xbgr8888,
wl_shm::Format::Argb8888,
wl_shm::Format::Xrgb8888,
]
}
}
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl", feature = "use_system_lib"))]
impl ImportEgl for Gles2Renderer {
fn import_egl_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
reader: &EGLBufferReader,
@ -654,8 +664,10 @@ impl Gles2Renderer {
Ok(texture)
}
}
#[cfg(feature = "wayland_frontend")]
impl Gles2Renderer {
fn existing_dmabuf_texture(
&self,
buffer: &wl_buffer::WlBuffer,
@ -880,16 +892,6 @@ impl Renderer for Gles2Renderer {
type TextureId = Gles2Texture;
type Frame = Gles2Frame;
#[cfg(feature = "wayland_frontend")]
fn shm_formats(&self) -> &[wl_shm::Format] {
&[
wl_shm::Format::Abgr8888,
wl_shm::Format::Xbgr8888,
wl_shm::Format::Argb8888,
wl_shm::Format::Xrgb8888,
]
}
#[cfg(feature = "image")]
fn import_bitmap<C: std::ops::Deref<Target = [u8]>>(
&mut self,
@ -935,40 +937,6 @@ impl Renderer for Gles2Renderer {
Ok(texture)
}
#[cfg(feature = "wayland_frontend")]
fn import_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
damage: &[Rectangle],
egl: Option<&EGLBufferReader>,
) -> Result<Self::TextureId, Self::Error> {
let texture = if egl.and_then(|egl| egl.egl_buffer_dimensions(&buffer)).is_some() {
self.import_egl(&buffer, egl.unwrap())
} else if crate::wayland::shm::with_buffer_contents(&buffer, |_, _| ()).is_ok() {
self.import_shm(&buffer, surface, damage)
} else {
Err(Gles2Error::UnknownBufferType)
}?;
// we want to keep the texture alive for as long as the buffer is alive.
// otherwise `existing_texture` will not work,
// if the user does not keep the texture alive long enough.
buffer.as_ref().user_data().set_threadsafe(|| UserDataMap::new());
if let Some(map) = buffer.as_ref().user_data().get::<UserDataMap>() {
map.insert_if_missing(|| Vec::<RefCell<Option<Gles2Texture>>>::new());
if let Some(vec) = map.get::<RefCell<Vec<Option<Gles2Texture>>>>() {
let mut vec = vec.borrow_mut();
while vec.len() < self.id {
vec.push(None);
}
vec[self.id] = Some(texture.clone());
}
}
Ok(texture)
}
fn render<F, R>(
&mut self,
width: u32,

View File

@ -18,7 +18,7 @@ use wayland_server::protocol::{wl_buffer, wl_shm};
#[cfg(feature = "renderer_gl")]
pub mod gles2;
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl"))]
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl", feature = "use_system_lib"))]
use crate::backend::egl::display::EGLBufferReader;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
@ -216,39 +216,6 @@ pub trait Renderer {
image: &image::ImageBuffer<image::Rgba<u8>, C>,
) -> Result<Self::TextureId, Self::Error>;
/// Returns supported formats for shared memory buffers.
///
/// Will always contain At least `Argb8888` and `Xrgb8888`.
#[cfg(feature = "wayland_frontend")]
fn shm_formats(&self) -> &[wl_shm::Format] {
// Mandatory
&[wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888]
}
/// Import a given buffer into the renderer.
///
/// Returns a texture_id, which can be used with `render_texture(_at)` or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
///
/// This operation needs no bound or default rendering target.
///
/// The implementation defines, if the id keeps being valid, if the buffer is released,
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
///
/// If provided the `SurfaceAttributes` can be used to do caching of rendering resources and is generally recommended.
///
/// The `damage` argument provides a list of rectangle locating parts of the buffer that need to be updated. When provided
/// with an empty list `&[]`, the renderer is allowed to not update the texture at all.
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl"))]
fn import_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
damage: &[Rectangle],
egl: Option<&EGLBufferReader>,
) -> Result<Self::TextureId, Self::Error>;
/// Initialize a rendering context on the current rendering target with given dimensions and transformation.
///
/// This function *may* error, if:
@ -267,6 +234,102 @@ pub trait Renderer {
F: FnOnce(&mut Self, &mut Self::Frame) -> R;
}
#[cfg(feature = "wayland_frontend")]
/// Trait for Renderers supporting importing shm-based buffers.
pub trait ImportShm: Renderer {
/// Import a given shm-based buffer into the renderer (see [`buffer_type`]).
///
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
/// or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
/// This operation needs no bound or default rendering target.
///
/// The implementation defines, if the id keeps being valid, if the buffer is released,
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
///
/// If provided the `SurfaceAttributes` can be used to do caching of rendering resources and is generally recommended.
///
/// The `damage` argument provides a list of rectangle locating parts of the buffer that need to be updated. When provided
/// with an empty list `&[]`, the renderer is allowed to not update the texture at all.
fn import_shm_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
damage: &[Rectangle],
) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>;
/// Returns supported formats for shared memory buffers.
///
/// Will always contain At least `Argb8888` and `Xrgb8888`.
fn shm_formats(&self) -> &[wl_shm::Format] {
// Mandatory
&[wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888]
}
}
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl", feature = "use_system_lib"))]
/// Trait for Renderers supporting importing wl_drm-based buffers.
pub trait ImportEgl: Renderer {
/// Import a given wl_drm-based buffer into the renderer (see [`buffer_type`]).
///
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
/// or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
///
/// This operation needs no bound or default rendering target.
///
/// The implementation defines, if the id keeps being valid, if the buffer is released,
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
fn import_egl_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
egl: &EGLBufferReader,
) -> Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>;
}
// TODO: Replace this with a trait_alias, once that is stabilized.
// pub type ImportAll = Renderer + ImportShm + ImportEgl;
/// Common trait for renderers of any wayland buffer type
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl", feature = "use_system_lib"))]
pub trait ImportAll: Renderer + ImportShm + ImportEgl {
/// Import a given buffer into the renderer.
///
/// Returns a texture_id, which can be used with [`Frame::render_texture`] (or [`Frame::render_texture_at`])
/// or implementation-specific functions.
///
/// If not otherwise defined by the implementation, this texture id is only valid for the renderer, that created it.
///
/// This operation needs no bound or default rendering target.
///
/// The implementation defines, if the id keeps being valid, if the buffer is released,
/// to avoid relying on implementation details, keep the buffer alive, until you destroyed this texture again.
///
/// If provided the `SurfaceAttributes` can be used to do caching of rendering resources and is generally recommended.
///
/// The `damage` argument provides a list of rectangle locating parts of the buffer that need to be updated. When provided
/// with an empty list `&[]`, the renderer is allowed to not update the texture at all.
///
/// Returns `None`, if the buffer type cannot be determined.
fn import_buffer(
&mut self,
buffer: &wl_buffer::WlBuffer,
surface: Option<&SurfaceAttributes>,
damage: &[Rectangle],
egl: Option<&EGLBufferReader>,
) -> Option<Result<<Self as Renderer>::TextureId, <Self as Renderer>::Error>> {
match buffer_type(buffer, egl) {
Some(BufferType::Shm) => Some(self.import_shm_buffer(buffer, surface, damage)),
Some(BufferType::Egl) => Some(self.import_egl_buffer(buffer, egl.unwrap())),
_ => None,
}
}
}
#[cfg(all(feature = "wayland_frontend", feature = "backend_egl", feature = "use_system_lib"))]
impl<R: Renderer + ImportShm + ImportEgl> ImportAll for R {}
#[cfg(feature = "wayland_frontend")]
#[non_exhaustive]
/// Buffer type of a given wl_buffer, if managed by smithay
@ -306,7 +369,7 @@ pub fn buffer_type(
/// Returns the *type* of a wl_buffer
///
/// Returns `None` if the type is not recognized by smithay or otherwise not supported.
#[cfg(all(feature = "wayland_frontend", not(feature = "backend_egl"), not(feature = "use_system_lib")))]
#[cfg(all(feature = "wayland_frontend", not(all(feature = "backend_egl", feature = "use_system_lib"))))]
pub fn buffer_type(buffer: &wl_buffer::WlBuffer) -> Option<BufferType> {
use crate::backend::allocator::Buffer;
@ -343,7 +406,7 @@ pub fn buffer_dimensions(
/// Returns the dimensions of a wl_buffer
///
/// *Note*: This will only return dimensions for buffer types known to smithay (see [`buffer_type`])
#[cfg(all(feature = "wayland_frontend", not(feature = "backend_egl"), not(feature = "use_system_lib")))]
#[cfg(all(feature = "wayland_frontend", not(all(feature = "backend_egl", feature = "use_system_lib"))))]
pub fn buffer_dimensions(buffer: &wl_buffer::WlBuffer) -> Option<(i32, i32)> {
use crate::backend::allocator::Buffer;

View File

@ -233,13 +233,13 @@ pub enum WinitEvent {
Refresh,
}
#[cfg(feature = "use_system_lib")]
impl WinitGraphicsBackend {
/// Bind a `wl_display` to allow hardware-accelerated clients using `wl_drm`.
///
/// Returns an `EGLBufferReader` used to access the contents of these buffers.
///
/// *Note*: Only on implementation of `wl_drm` can be bound by a single wayland display.
#[cfg(feature = "use_system_lib")]
pub fn bind_wl_display(&self, wl_display: &Display) -> Result<EGLBufferReader, EGLError> {
self.display.bind_wl_display(wl_display)
}