1308 lines
37 KiB
Rust
1308 lines
37 KiB
Rust
use {
|
|
crate::{
|
|
allocator::Allocator,
|
|
cmm::{
|
|
cmm_description::{ColorDescription, LinearColorDescription},
|
|
cmm_render_intent::RenderIntent,
|
|
},
|
|
cpu_worker::CpuWorker,
|
|
cursor::Cursor,
|
|
damage::DamageVisualizer,
|
|
eventfd_cache::Eventfd,
|
|
fixed::Fixed,
|
|
format::Format,
|
|
io_uring::{IoUring, IoUringError},
|
|
rect::{Rect, Region},
|
|
renderer::{Renderer, renderer_base::RendererBase},
|
|
scale::Scale,
|
|
state::State,
|
|
theme::Color,
|
|
tree::{Node, OutputNode, Transform},
|
|
utils::{
|
|
clonecell::UnsafeCellCloneSafe, errorfmt::ErrorFmt, oserror::OsErrorExt,
|
|
static_text::StaticText,
|
|
},
|
|
video::{
|
|
Modifier,
|
|
dmabuf::DmaBuf,
|
|
drm::syncobj::{Syncobj, SyncobjCtx, SyncobjPoint},
|
|
},
|
|
},
|
|
ahash::AHashMap,
|
|
indexmap::{IndexMap, IndexSet},
|
|
jay_config::video::GfxApi as ConfigGfxApi,
|
|
linearize::Linearize,
|
|
std::{
|
|
any::Any,
|
|
cell::{Cell, OnceCell},
|
|
error::Error,
|
|
ffi::CString,
|
|
fmt::{Debug, Formatter},
|
|
ops::Deref,
|
|
rc::Rc,
|
|
slice,
|
|
sync::atomic::{AtomicU64, Ordering::Relaxed},
|
|
},
|
|
thiserror::Error,
|
|
uapi::{OwnedFd, c},
|
|
};
|
|
|
|
pub use jay_gfx_types::{AlphaMode, ShmMemory, ShmMemoryBacking};
|
|
|
|
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Linearize)]
|
|
pub enum GfxApi {
|
|
OpenGl,
|
|
Vulkan,
|
|
}
|
|
|
|
impl StaticText for GfxApi {
|
|
fn text(&self) -> &'static str {
|
|
self.to_str()
|
|
}
|
|
}
|
|
|
|
impl TryFrom<ConfigGfxApi> for GfxApi {
|
|
type Error = ();
|
|
|
|
fn try_from(value: ConfigGfxApi) -> Result<Self, Self::Error> {
|
|
let v = match value {
|
|
ConfigGfxApi::OpenGl => GfxApi::OpenGl,
|
|
ConfigGfxApi::Vulkan => GfxApi::Vulkan,
|
|
_ => return Err(()),
|
|
};
|
|
Ok(v)
|
|
}
|
|
}
|
|
|
|
impl Into<ConfigGfxApi> for GfxApi {
|
|
fn into(self) -> ConfigGfxApi {
|
|
match self {
|
|
GfxApi::OpenGl => ConfigGfxApi::OpenGl,
|
|
GfxApi::Vulkan => ConfigGfxApi::Vulkan,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GfxApi {
|
|
pub fn to_str(&self) -> &'static str {
|
|
match self {
|
|
GfxApi::OpenGl => "OpenGl",
|
|
GfxApi::Vulkan => "Vulkan",
|
|
}
|
|
}
|
|
|
|
pub fn from_str_lossy(s: &str) -> Option<Self> {
|
|
match &*s.to_ascii_lowercase() {
|
|
"opengl" => Some(Self::OpenGl),
|
|
"vulkan" => Some(Self::Vulkan),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum GfxApiOpt {
|
|
Sync,
|
|
FillRect(FillRect),
|
|
CopyTexture(CopyTexture),
|
|
RoundedFillRect(RoundedFillRect),
|
|
RoundedCopyTexture(RoundedCopyTexture),
|
|
}
|
|
|
|
pub struct GfxRenderPass {
|
|
pub ops: Vec<GfxApiOpt>,
|
|
pub clear: Option<Color>,
|
|
pub clear_cd: Rc<LinearColorDescription>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Copy, Clone, PartialEq)]
|
|
pub struct SampleRect {
|
|
pub x1: f32,
|
|
pub y1: f32,
|
|
pub x2: f32,
|
|
pub y2: f32,
|
|
pub buffer_transform: Transform,
|
|
}
|
|
|
|
impl SampleRect {
|
|
pub fn identity() -> Self {
|
|
Self {
|
|
x1: 0.0,
|
|
y1: 0.0,
|
|
x2: 1.0,
|
|
y2: 1.0,
|
|
buffer_transform: Transform::None,
|
|
}
|
|
}
|
|
|
|
pub fn is_covering(&self) -> bool {
|
|
self.x1 == 0.0 && self.y1 == 0.0 && self.x2 == 1.0 && self.y2 == 1.0
|
|
}
|
|
|
|
pub fn to_points(&self) -> [[f32; 2]; 4] {
|
|
use Transform::*;
|
|
let x1 = self.x1;
|
|
let x2 = self.x2;
|
|
let y1 = self.y1;
|
|
let y2 = self.y2;
|
|
match self.buffer_transform {
|
|
None => [[x2, y1], [x1, y1], [x2, y2], [x1, y2]],
|
|
Rotate90 => [
|
|
[y1, 1.0 - x2],
|
|
[y1, 1.0 - x1],
|
|
[y2, 1.0 - x2],
|
|
[y2, 1.0 - x1],
|
|
],
|
|
Rotate180 => [
|
|
[1.0 - x2, 1.0 - y1],
|
|
[1.0 - x1, 1.0 - y1],
|
|
[1.0 - x2, 1.0 - y2],
|
|
[1.0 - x1, 1.0 - y2],
|
|
],
|
|
Rotate270 => [
|
|
[1.0 - y1, x2],
|
|
[1.0 - y1, x1],
|
|
[1.0 - y2, x2],
|
|
[1.0 - y2, x1],
|
|
],
|
|
Flip => [
|
|
[1.0 - x2, y1],
|
|
[1.0 - x1, y1],
|
|
[1.0 - x2, y2],
|
|
[1.0 - x1, y2],
|
|
],
|
|
FlipRotate90 => [[y1, x2], [y1, x1], [y2, x2], [y2, x1]],
|
|
FlipRotate180 => [
|
|
[x2, 1.0 - y1],
|
|
[x1, 1.0 - y1],
|
|
[x2, 1.0 - y2],
|
|
[x1, 1.0 - y2],
|
|
],
|
|
FlipRotate270 => [
|
|
[1.0 - y1, 1.0 - x2],
|
|
[1.0 - y1, 1.0 - x1],
|
|
[1.0 - y2, 1.0 - x2],
|
|
[1.0 - y2, 1.0 - x1],
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub struct FramebufferRect {
|
|
pub x1: f32,
|
|
pub x2: f32,
|
|
pub y1: f32,
|
|
pub y2: f32,
|
|
pub output_transform: Transform,
|
|
}
|
|
|
|
impl FramebufferRect {
|
|
pub fn new(
|
|
x1: f32,
|
|
y1: f32,
|
|
x2: f32,
|
|
y2: f32,
|
|
transform: Transform,
|
|
width: f32,
|
|
height: f32,
|
|
) -> Self {
|
|
Self {
|
|
x1: 2.0 * x1 / width - 1.0,
|
|
x2: 2.0 * x2 / width - 1.0,
|
|
y1: 2.0 * y1 / height - 1.0,
|
|
y2: 2.0 * y2 / height - 1.0,
|
|
output_transform: transform,
|
|
}
|
|
}
|
|
|
|
pub fn to_points(&self) -> [[f32; 2]; 4] {
|
|
use Transform::*;
|
|
let x1 = self.x1;
|
|
let x2 = self.x2;
|
|
let y1 = self.y1;
|
|
let y2 = self.y2;
|
|
match self.output_transform {
|
|
None => [[x2, y1], [x1, y1], [x2, y2], [x1, y2]],
|
|
Rotate90 => [[y1, -x2], [y1, -x1], [y2, -x2], [y2, -x1]],
|
|
Rotate180 => [[-x2, -y1], [-x1, -y1], [-x2, -y2], [-x1, -y2]],
|
|
Rotate270 => [[-y1, x2], [-y1, x1], [-y2, x2], [-y2, x1]],
|
|
Flip => [[-x2, y1], [-x1, y1], [-x2, y2], [-x1, y2]],
|
|
FlipRotate90 => [[y1, x2], [y1, x1], [y2, x2], [y2, x1]],
|
|
FlipRotate180 => [[x2, -y1], [x1, -y1], [x2, -y2], [x1, -y2]],
|
|
FlipRotate270 => [[-y1, -x2], [-y1, -x1], [-y2, -x2], [-y2, -x1]],
|
|
}
|
|
}
|
|
|
|
pub fn is_covering(&self) -> bool {
|
|
self.x1 == -1.0 && self.y1 == -1.0 && self.x2 == 1.0 && self.y2 == 1.0
|
|
}
|
|
|
|
pub fn to_rect(&self, width: f32, height: f32) -> Rect {
|
|
let mut x1 = self.x1;
|
|
let mut x2 = self.x2;
|
|
let mut y1 = self.y1;
|
|
let mut y2 = self.y2;
|
|
(x1, y1, x2, y2) = match self.output_transform {
|
|
Transform::None => (x1, y1, x2, y2),
|
|
Transform::Rotate90 => (y1, -x2, y2, -x1),
|
|
Transform::Rotate180 => (-x2, -y2, -x1, -y1),
|
|
Transform::Rotate270 => (-y2, x1, -y1, x2),
|
|
Transform::Flip => (-x2, y1, -x1, y2),
|
|
Transform::FlipRotate90 => (y1, x1, y2, x2),
|
|
Transform::FlipRotate180 => (x1, -y2, x2, -y1),
|
|
Transform::FlipRotate270 => (-y2, -x2, -y1, -x1),
|
|
};
|
|
let x1 = ((x1 + 1.0) / 2.0 * width).round() as i32;
|
|
let x2 = ((x2 + 1.0) / 2.0 * width).round() as i32;
|
|
let y1 = ((y1 + 1.0) / 2.0 * height).round() as i32;
|
|
let y2 = ((y2 + 1.0) / 2.0 * height).round() as i32;
|
|
Rect::new_saturating(x1, y1, x2, y2)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct FillRect {
|
|
pub rect: FramebufferRect,
|
|
pub color: Color,
|
|
pub alpha: Option<f32>,
|
|
pub render_intent: RenderIntent,
|
|
pub cd: Rc<LinearColorDescription>,
|
|
}
|
|
|
|
impl FillRect {
|
|
pub fn effective_color(&self) -> Color {
|
|
let mut color = self.color;
|
|
if let Some(alpha) = self.alpha {
|
|
color = color * alpha;
|
|
}
|
|
color
|
|
}
|
|
}
|
|
|
|
pub struct CopyTexture {
|
|
pub tex: Rc<dyn GfxTexture>,
|
|
pub source: SampleRect,
|
|
pub target: FramebufferRect,
|
|
pub buffer_resv: Option<Rc<dyn BufferResv>>,
|
|
pub acquire_sync: AcquireSync,
|
|
pub release_sync: ReleaseSync,
|
|
pub alpha: Option<f32>,
|
|
pub opaque: bool,
|
|
pub render_intent: RenderIntent,
|
|
pub cd: Rc<ColorDescription>,
|
|
pub alpha_mode: AlphaMode,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct RoundedFillRect {
|
|
pub rect: FramebufferRect,
|
|
pub color: Color,
|
|
pub alpha: Option<f32>,
|
|
pub render_intent: RenderIntent,
|
|
pub cd: Rc<LinearColorDescription>,
|
|
/// Size of the rectangle in physical pixels.
|
|
pub size: [f32; 2],
|
|
/// Per-corner radius in physical pixels: [top_left, top_right, bottom_right, bottom_left].
|
|
pub corner_radius: [f32; 4],
|
|
/// Border width in physical pixels. 0 means a filled rounded rect (no cutout).
|
|
pub border_width: f32,
|
|
/// Output scale for antialiasing.
|
|
pub scale: f32,
|
|
/// Sort order hint within the RoundedFill bucket (lower renders first).
|
|
pub z_order: u32,
|
|
}
|
|
|
|
pub struct RoundedCopyTexture {
|
|
pub tex: Rc<dyn GfxTexture>,
|
|
pub source: SampleRect,
|
|
pub target: FramebufferRect,
|
|
pub buffer_resv: Option<Rc<dyn BufferResv>>,
|
|
pub acquire_sync: AcquireSync,
|
|
pub release_sync: ReleaseSync,
|
|
pub alpha: Option<f32>,
|
|
pub render_intent: RenderIntent,
|
|
pub cd: Rc<ColorDescription>,
|
|
pub alpha_mode: AlphaMode,
|
|
/// Size of the rectangle in physical pixels.
|
|
pub size: [f32; 2],
|
|
/// Per-corner radius in physical pixels: [top_left, top_right, bottom_right, bottom_left].
|
|
pub corner_radius: [f32; 4],
|
|
/// Output scale for antialiasing.
|
|
pub scale: f32,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SyncFile(pub Rc<OwnedFd>);
|
|
|
|
impl Deref for SyncFile {
|
|
type Target = Rc<OwnedFd>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
unsafe impl UnsafeCellCloneSafe for SyncFile {}
|
|
|
|
#[derive(Clone)]
|
|
pub enum AcquireSync {
|
|
None,
|
|
Implicit,
|
|
FdSync(FdSync),
|
|
Unnecessary,
|
|
}
|
|
|
|
impl AcquireSync {
|
|
pub fn from_fd_sync(sync: Option<FdSync>) -> Self {
|
|
match sync {
|
|
None => Self::Unnecessary,
|
|
Some(sync) => Self::FdSync(sync),
|
|
}
|
|
}
|
|
|
|
pub fn get_sync_file(&self) -> Option<&SyncFile> {
|
|
match self {
|
|
Self::None => None,
|
|
Self::Implicit => None,
|
|
Self::FdSync(sync) => sync.get_sync_file(),
|
|
Self::Unnecessary => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
|
pub enum ReleaseSync {
|
|
None,
|
|
Implicit,
|
|
Explicit,
|
|
}
|
|
|
|
impl Debug for AcquireSync {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
let name = match self {
|
|
AcquireSync::None => "None",
|
|
AcquireSync::Implicit => "Implicit",
|
|
AcquireSync::FdSync(d) => return Debug::fmt(d, f),
|
|
AcquireSync::Unnecessary => "Unnecessary",
|
|
};
|
|
f.debug_struct(name).finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
pub trait BufferResv: Debug {
|
|
fn set_sync(&self, user: BufferResvUser, sync: &FdSync);
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
|
pub struct BufferResvUser(u64);
|
|
|
|
impl Default for BufferResvUser {
|
|
fn default() -> Self {
|
|
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
|
Self(NEXT_ID.fetch_add(1, Relaxed))
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
pub enum ResetStatus {
|
|
Guilty,
|
|
Innocent,
|
|
Unknown,
|
|
Other(u32),
|
|
}
|
|
|
|
pub trait GfxBlendBuffer: Any + Debug {}
|
|
|
|
pub trait GfxFramebuffer: Debug {
|
|
fn physical_size(&self) -> (i32, i32);
|
|
|
|
fn render_with_region(
|
|
self: Rc<Self>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
cd: &Rc<ColorDescription>,
|
|
ops: &[GfxApiOpt],
|
|
clear: Option<&Color>,
|
|
clear_cd: &Rc<LinearColorDescription>,
|
|
region: &Region,
|
|
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
|
|
blend_cd: &Rc<ColorDescription>,
|
|
) -> Result<Option<FdSync>, GfxError>;
|
|
|
|
fn format(&self) -> &'static Format;
|
|
|
|
fn full_region(&self) -> Region {
|
|
let (width, height) = self.physical_size();
|
|
Region::new(Rect::new_sized_saturating(0, 0, width, height))
|
|
}
|
|
}
|
|
|
|
pub trait GfxInternalFramebuffer: GfxFramebuffer {
|
|
fn stride(&self) -> i32;
|
|
|
|
fn staging_size(&self) -> usize;
|
|
|
|
fn download(
|
|
self: Rc<Self>,
|
|
staging: &Rc<dyn GfxStagingBuffer>,
|
|
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
|
mem: Rc<dyn ShmMemory>,
|
|
damage: Region,
|
|
) -> Result<Option<PendingShmTransfer>, GfxError>;
|
|
}
|
|
|
|
impl dyn GfxFramebuffer {
|
|
pub fn render(
|
|
self: &Rc<Self>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
cd: &Rc<ColorDescription>,
|
|
ops: &[GfxApiOpt],
|
|
clear: Option<&Color>,
|
|
clear_cd: &Rc<LinearColorDescription>,
|
|
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
|
|
blend_cd: &Rc<ColorDescription>,
|
|
) -> Result<Option<FdSync>, GfxError> {
|
|
self.clone().render_with_region(
|
|
acquire_sync,
|
|
release_sync,
|
|
cd,
|
|
ops,
|
|
clear,
|
|
clear_cd,
|
|
&self.full_region(),
|
|
blend_buffer,
|
|
blend_cd,
|
|
)
|
|
}
|
|
|
|
pub fn clear(
|
|
self: &Rc<Self>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
cd: &Rc<ColorDescription>,
|
|
) -> Result<Option<FdSync>, GfxError> {
|
|
self.clear_with(
|
|
acquire_sync,
|
|
release_sync,
|
|
cd,
|
|
&Color::TRANSPARENT,
|
|
&cd.linear,
|
|
)
|
|
}
|
|
|
|
pub fn clear_with(
|
|
self: &Rc<Self>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
cd: &Rc<ColorDescription>,
|
|
color: &Color,
|
|
color_cd: &Rc<LinearColorDescription>,
|
|
) -> Result<Option<FdSync>, GfxError> {
|
|
self.render(
|
|
acquire_sync,
|
|
release_sync,
|
|
cd,
|
|
&[],
|
|
Some(color),
|
|
color_cd,
|
|
None,
|
|
cd,
|
|
)
|
|
}
|
|
|
|
pub fn logical_size(&self, transform: Transform) -> (i32, i32) {
|
|
logical_size(self.physical_size(), transform)
|
|
}
|
|
|
|
pub fn renderer_base<'a>(
|
|
&self,
|
|
ops: &'a mut Vec<GfxApiOpt>,
|
|
scale: Scale,
|
|
transform: Transform,
|
|
) -> RendererBase<'a> {
|
|
renderer_base(self.physical_size(), ops, scale, transform)
|
|
}
|
|
|
|
pub fn copy_texture(
|
|
self: &Rc<Self>,
|
|
fb_acquire_sync: AcquireSync,
|
|
fb_release_sync: ReleaseSync,
|
|
fb_cd: &Rc<ColorDescription>,
|
|
texture: &Rc<dyn GfxTexture>,
|
|
texture_cd: &Rc<ColorDescription>,
|
|
resv: Option<&Rc<dyn BufferResv>>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
x: i32,
|
|
y: i32,
|
|
) -> Result<Option<FdSync>, GfxError> {
|
|
let mut ops = vec![];
|
|
let scale = Scale::from_int(1);
|
|
let mut renderer = self.renderer_base(&mut ops, scale, Transform::None);
|
|
renderer.render_texture(
|
|
texture,
|
|
None,
|
|
x,
|
|
y,
|
|
None,
|
|
None,
|
|
scale,
|
|
None,
|
|
resv.cloned(),
|
|
acquire_sync,
|
|
release_sync,
|
|
false,
|
|
texture_cd,
|
|
RenderIntent::Perceptual,
|
|
AlphaMode::PremultipliedElectrical,
|
|
);
|
|
let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT);
|
|
self.render(
|
|
fb_acquire_sync,
|
|
fb_release_sync,
|
|
fb_cd,
|
|
&ops,
|
|
clear,
|
|
&fb_cd.linear,
|
|
None,
|
|
fb_cd,
|
|
)
|
|
}
|
|
|
|
pub fn create_render_pass(
|
|
&self,
|
|
node: &dyn Node,
|
|
state: &State,
|
|
cursor_rect: Option<Rect>,
|
|
scale: Scale,
|
|
render_cursor: bool,
|
|
render_hardware_cursor: bool,
|
|
black_background: bool,
|
|
fill_black_in_grace_period: bool,
|
|
transform: Transform,
|
|
visualizer: Option<&DamageVisualizer>,
|
|
) -> GfxRenderPass {
|
|
create_render_pass(
|
|
self.physical_size(),
|
|
node,
|
|
state,
|
|
cursor_rect,
|
|
scale,
|
|
render_cursor,
|
|
render_hardware_cursor,
|
|
black_background,
|
|
fill_black_in_grace_period,
|
|
transform,
|
|
visualizer,
|
|
)
|
|
}
|
|
|
|
pub fn perform_render_pass(
|
|
self: &Rc<Self>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
cd: &Rc<ColorDescription>,
|
|
pass: &GfxRenderPass,
|
|
region: &Region,
|
|
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
|
|
blend_cd: &Rc<ColorDescription>,
|
|
) -> Result<Option<FdSync>, GfxError> {
|
|
self.clone().render_with_region(
|
|
acquire_sync,
|
|
release_sync,
|
|
cd,
|
|
&pass.ops,
|
|
pass.clear.as_ref(),
|
|
&pass.clear_cd,
|
|
region,
|
|
blend_buffer,
|
|
blend_cd,
|
|
)
|
|
}
|
|
|
|
pub fn render_output(
|
|
self: &Rc<Self>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
cd: &Rc<ColorDescription>,
|
|
node: &OutputNode,
|
|
state: &State,
|
|
cursor_rect: Option<Rect>,
|
|
scale: Scale,
|
|
render_hardware_cursor: bool,
|
|
fill_black_in_grace_period: bool,
|
|
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
|
|
blend_cd: &Rc<ColorDescription>,
|
|
) -> Result<Option<FdSync>, GfxError> {
|
|
self.render_node(
|
|
acquire_sync,
|
|
release_sync,
|
|
cd,
|
|
node,
|
|
state,
|
|
cursor_rect,
|
|
scale,
|
|
true,
|
|
render_hardware_cursor,
|
|
node.has_fullscreen(),
|
|
fill_black_in_grace_period,
|
|
node.global.persistent.transform.get(),
|
|
blend_buffer,
|
|
blend_cd,
|
|
)
|
|
}
|
|
|
|
pub fn render_node(
|
|
self: &Rc<Self>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
cd: &Rc<ColorDescription>,
|
|
node: &dyn Node,
|
|
state: &State,
|
|
cursor_rect: Option<Rect>,
|
|
scale: Scale,
|
|
render_cursor: bool,
|
|
render_hardware_cursor: bool,
|
|
black_background: bool,
|
|
fill_black_in_grace_period: bool,
|
|
transform: Transform,
|
|
blend_buffer: Option<&Rc<dyn GfxBlendBuffer>>,
|
|
blend_cd: &Rc<ColorDescription>,
|
|
) -> Result<Option<FdSync>, GfxError> {
|
|
let pass = self.create_render_pass(
|
|
node,
|
|
state,
|
|
cursor_rect,
|
|
scale,
|
|
render_cursor,
|
|
render_hardware_cursor,
|
|
black_background,
|
|
fill_black_in_grace_period,
|
|
transform,
|
|
None,
|
|
);
|
|
self.perform_render_pass(
|
|
acquire_sync,
|
|
release_sync,
|
|
cd,
|
|
&pass,
|
|
&self.full_region(),
|
|
blend_buffer,
|
|
blend_cd,
|
|
)
|
|
}
|
|
|
|
pub fn render_hardware_cursor(
|
|
self: &Rc<Self>,
|
|
acquire_sync: AcquireSync,
|
|
release_sync: ReleaseSync,
|
|
cursor: &dyn Cursor,
|
|
state: &State,
|
|
scale: Scale,
|
|
transform: Transform,
|
|
cd: &Rc<ColorDescription>,
|
|
) -> Result<Option<FdSync>, GfxError> {
|
|
let mut ops = vec![];
|
|
let mut renderer = Renderer {
|
|
base: self.renderer_base(&mut ops, scale, transform),
|
|
state,
|
|
logical_extents: Rect::new_empty(0, 0),
|
|
pixel_extents: {
|
|
let (width, height) = self.logical_size(transform);
|
|
Rect::new_saturating(0, 0, width, height)
|
|
},
|
|
stretch: None,
|
|
corner_radius: None,
|
|
};
|
|
cursor.render_hardware_cursor(&mut renderer);
|
|
self.render(
|
|
acquire_sync,
|
|
release_sync,
|
|
cd,
|
|
&ops,
|
|
Some(&Color::TRANSPARENT),
|
|
&cd.linear,
|
|
None,
|
|
cd,
|
|
)
|
|
}
|
|
}
|
|
|
|
pub trait GfxImage {
|
|
fn to_framebuffer(self: Rc<Self>) -> Result<Rc<dyn GfxFramebuffer>, GfxError>;
|
|
|
|
fn to_texture(self: Rc<Self>) -> Result<Rc<dyn GfxTexture>, GfxError>;
|
|
|
|
fn width(&self) -> i32;
|
|
fn height(&self) -> i32;
|
|
}
|
|
|
|
pub trait GfxTexture: Any + Debug {
|
|
fn size(&self) -> (i32, i32);
|
|
fn dmabuf(&self) -> Option<&DmaBuf>;
|
|
fn format(&self) -> &'static Format;
|
|
}
|
|
|
|
pub trait ShmGfxTexture: GfxTexture {}
|
|
|
|
pub trait AsyncShmGfxTextureCallback {
|
|
fn completed(self: Rc<Self>, res: Result<(), GfxError>);
|
|
}
|
|
|
|
bitflags! {
|
|
StagingBufferUsecase: u32;
|
|
STAGING_UPLOAD,
|
|
STAGING_DOWNLOAD,
|
|
}
|
|
|
|
pub trait GfxStagingBuffer: Any {
|
|
fn size(&self) -> usize;
|
|
}
|
|
|
|
pub trait GfxBuffer: Any {}
|
|
|
|
pub trait AsyncShmGfxTextureTransferCancellable {
|
|
fn cancel(&self, id: u64);
|
|
}
|
|
|
|
pub struct PendingShmTransfer {
|
|
cancel: Rc<dyn AsyncShmGfxTextureTransferCancellable>,
|
|
id: u64,
|
|
}
|
|
|
|
pub trait AsyncShmGfxTexture: GfxTexture {
|
|
fn staging_size(&self) -> usize {
|
|
0
|
|
}
|
|
|
|
fn async_upload(
|
|
self: Rc<Self>,
|
|
staging: &Rc<dyn GfxStagingBuffer>,
|
|
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
|
mem: Rc<dyn ShmMemory>,
|
|
damage: Region,
|
|
) -> Result<Option<PendingShmTransfer>, GfxError>;
|
|
|
|
fn async_upload_from_buffer(
|
|
self: Rc<Self>,
|
|
buf: &Rc<dyn GfxBuffer>,
|
|
callback: Rc<dyn AsyncShmGfxTextureCallback>,
|
|
damage: Region,
|
|
) -> Result<Option<PendingShmTransfer>, GfxError> {
|
|
let _ = buf;
|
|
let _ = callback;
|
|
let _ = damage;
|
|
|
|
#[derive(Debug, Error)]
|
|
#[error("Host buffers are not supported")]
|
|
struct E;
|
|
Err(GfxError(Box::new(E)))
|
|
}
|
|
|
|
fn sync_upload(self: Rc<Self>, shm: &[Cell<u8>], damage: Region) -> Result<(), GfxError>;
|
|
|
|
fn compatible_with(
|
|
&self,
|
|
format: &'static Format,
|
|
width: i32,
|
|
height: i32,
|
|
stride: i32,
|
|
) -> bool;
|
|
}
|
|
|
|
pub trait GfxContext: Debug {
|
|
fn reset_status(&self) -> Option<ResetStatus>;
|
|
|
|
fn render_node(&self) -> Option<Rc<CString>>;
|
|
|
|
fn formats(&self) -> &Rc<AHashMap<u32, GfxFormat>>;
|
|
|
|
fn fast_ram_access(&self) -> bool;
|
|
|
|
fn dmabuf_fb(self: Rc<Self>, buf: &DmaBuf) -> Result<Rc<dyn GfxFramebuffer>, GfxError> {
|
|
self.dmabuf_img(buf)?.to_framebuffer()
|
|
}
|
|
|
|
fn dmabuf_img(self: Rc<Self>, buf: &DmaBuf) -> Result<Rc<dyn GfxImage>, GfxError>;
|
|
|
|
fn shmem_texture(
|
|
self: Rc<Self>,
|
|
old: Option<Rc<dyn ShmGfxTexture>>,
|
|
data: &[Cell<u8>],
|
|
format: &'static Format,
|
|
width: i32,
|
|
height: i32,
|
|
stride: i32,
|
|
damage: Option<&[Rect]>,
|
|
) -> Result<Rc<dyn ShmGfxTexture>, GfxError>;
|
|
|
|
fn async_shmem_texture(
|
|
self: Rc<Self>,
|
|
format: &'static Format,
|
|
width: i32,
|
|
height: i32,
|
|
stride: i32,
|
|
cpu_worker: &Rc<CpuWorker>,
|
|
) -> Result<Rc<dyn AsyncShmGfxTexture>, GfxError>;
|
|
|
|
fn allocator(&self) -> Rc<dyn Allocator>;
|
|
|
|
fn gfx_api(&self) -> GfxApi;
|
|
|
|
fn create_internal_fb(
|
|
self: Rc<Self>,
|
|
cpu_worker: &Rc<CpuWorker>,
|
|
width: i32,
|
|
height: i32,
|
|
stride: i32,
|
|
format: &'static Format,
|
|
) -> Result<Rc<dyn GfxInternalFramebuffer>, GfxError>;
|
|
|
|
fn syncobj_ctx(&self) -> Option<&Rc<SyncobjCtx>>;
|
|
|
|
fn create_staging_buffer(
|
|
&self,
|
|
size: usize,
|
|
usecase: StagingBufferUsecase,
|
|
) -> Rc<dyn GfxStagingBuffer> {
|
|
let _ = usecase;
|
|
struct Dummy(usize);
|
|
impl GfxStagingBuffer for Dummy {
|
|
fn size(&self) -> usize {
|
|
self.0
|
|
}
|
|
}
|
|
Rc::new(Dummy(size))
|
|
}
|
|
|
|
fn acquire_blend_buffer(
|
|
&self,
|
|
width: i32,
|
|
height: i32,
|
|
) -> Result<Rc<dyn GfxBlendBuffer>, GfxError>;
|
|
|
|
fn supports_color_management(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn supports_alpha_modes(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn supports_invalid_modifier(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn create_dmabuf_buffer(
|
|
&self,
|
|
dmabuf: &OwnedFd,
|
|
offset: usize,
|
|
size: usize,
|
|
format: &'static Format,
|
|
) -> Result<Rc<dyn GfxBuffer>, GfxError> {
|
|
let _ = dmabuf;
|
|
let _ = offset;
|
|
let _ = size;
|
|
let _ = format;
|
|
|
|
#[derive(Debug, Error)]
|
|
#[error("Host buffers are not supported")]
|
|
struct E;
|
|
Err(GfxError(Box::new(E)))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct GfxWriteModifier {
|
|
pub needs_render_usage: bool,
|
|
}
|
|
|
|
pub fn needs_render_usage<'a>(mut modifiers: impl Iterator<Item = &'a GfxWriteModifier>) -> bool {
|
|
modifiers.any(|m| m.needs_render_usage)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct GfxFormat {
|
|
pub format: &'static Format,
|
|
pub read_modifiers: IndexSet<Modifier>,
|
|
pub write_modifiers: IndexMap<Modifier, GfxWriteModifier>,
|
|
pub supports_shm: bool,
|
|
}
|
|
|
|
#[derive(Error)]
|
|
#[error(transparent)]
|
|
pub struct GfxError(pub Box<dyn Error + Send>);
|
|
|
|
impl Debug for GfxError {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
Debug::fmt(&self.0, f)
|
|
}
|
|
}
|
|
|
|
impl PendingShmTransfer {
|
|
pub fn new(cancel: Rc<dyn AsyncShmGfxTextureTransferCancellable>, id: u64) -> Self {
|
|
Self { cancel, id }
|
|
}
|
|
}
|
|
|
|
impl Drop for PendingShmTransfer {
|
|
fn drop(&mut self) {
|
|
self.cancel.cancel(self.id);
|
|
}
|
|
}
|
|
|
|
pub fn create_render_pass(
|
|
physical_size: (i32, i32),
|
|
node: &dyn Node,
|
|
state: &State,
|
|
cursor_rect: Option<Rect>,
|
|
scale: Scale,
|
|
render_cursor: bool,
|
|
render_hardware_cursor: bool,
|
|
black_background: bool,
|
|
fill_black_in_grace_period: bool,
|
|
transform: Transform,
|
|
visualizer: Option<&DamageVisualizer>,
|
|
) -> GfxRenderPass {
|
|
if fill_black_in_grace_period && state.idle.in_grace_period.get() {
|
|
return GfxRenderPass {
|
|
ops: vec![],
|
|
clear: Some(Color::SOLID_BLACK),
|
|
clear_cd: state.color_manager.srgb_gamma22().linear.clone(),
|
|
};
|
|
}
|
|
let mut ops = vec![];
|
|
let mut renderer = Renderer {
|
|
base: renderer_base(physical_size, &mut ops, scale, transform),
|
|
state,
|
|
logical_extents: node.node_absolute_position().at_point(0, 0),
|
|
pixel_extents: {
|
|
let (width, height) = logical_size(physical_size, transform);
|
|
Rect::new_saturating(0, 0, width, height)
|
|
},
|
|
stretch: None,
|
|
corner_radius: None,
|
|
};
|
|
node.node_render(&mut renderer, 0, 0, None);
|
|
if let Some(rect) = cursor_rect {
|
|
let seats = state.globals.lock_seats();
|
|
for seat in seats.values() {
|
|
let (x, y) = seat.pointer_cursor().position_int();
|
|
if let Some(im) = seat.input_method() {
|
|
for (_, popup) in im.popups() {
|
|
if popup.surface.node_visible() {
|
|
let pos = popup.surface.buffer_abs_pos.get();
|
|
let extents = popup.surface.extents.get().move_(pos.x1(), pos.y1());
|
|
if extents.intersects(&rect) {
|
|
let (x, y) = rect.translate(pos.x1(), pos.y1());
|
|
renderer.render_surface(&popup.surface, x, y, None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let Some(highlight) = seat.ui_drag_highlight() {
|
|
renderer.render_highlight(&highlight.move_(-rect.x1(), -rect.y1()));
|
|
}
|
|
if let Some(drag) = seat.toplevel_drag() {
|
|
drag.render(&mut renderer, &rect, x, y);
|
|
}
|
|
if let Some(dnd_icon) = seat.dnd_icon() {
|
|
dnd_icon.render(&mut renderer, &rect, x, y);
|
|
}
|
|
if render_cursor {
|
|
let cursor_user_group = seat.cursor_group();
|
|
if (render_hardware_cursor || !cursor_user_group.hardware_cursor())
|
|
&& let Some(cursor_user) = cursor_user_group.active()
|
|
&& let Some(cursor) = cursor_user.get()
|
|
{
|
|
cursor.tick();
|
|
let (mut x, mut y) = cursor_user.position();
|
|
x -= Fixed::from_int(rect.x1());
|
|
y -= Fixed::from_int(rect.y1());
|
|
cursor.render(&mut renderer, x, y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let Some(visualizer) = visualizer
|
|
&& let Some(cursor_rect) = cursor_rect
|
|
{
|
|
visualizer.render(&cursor_rect, &mut renderer.base);
|
|
}
|
|
let c = match black_background {
|
|
true => Color::SOLID_BLACK,
|
|
false => state.theme.colors.background.get(),
|
|
};
|
|
GfxRenderPass {
|
|
ops,
|
|
clear: Some(c),
|
|
clear_cd: state.color_manager.srgb_gamma22().linear.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn renderer_base<'a>(
|
|
physical_size: (i32, i32),
|
|
ops: &'a mut Vec<GfxApiOpt>,
|
|
scale: Scale,
|
|
transform: Transform,
|
|
) -> RendererBase<'a> {
|
|
let (width, height) = logical_size(physical_size, transform);
|
|
RendererBase {
|
|
ops,
|
|
scaled: scale != 1,
|
|
scale,
|
|
scalef: scale.to_f64(),
|
|
transform,
|
|
fb_width: width as _,
|
|
fb_height: height as _,
|
|
}
|
|
}
|
|
|
|
pub fn logical_size(physical_size: (i32, i32), transform: Transform) -> (i32, i32) {
|
|
transform.maybe_swap(physical_size)
|
|
}
|
|
|
|
pub struct ReservedSyncobjPoint {
|
|
pub ctx: Rc<SyncobjCtx>,
|
|
pub syncobj: Rc<Syncobj>,
|
|
pub point: SyncobjPoint,
|
|
pub sync_file: OnceCell<Option<SyncFile>>,
|
|
pub signaled: Eventfd,
|
|
}
|
|
|
|
impl Debug for ReservedSyncobjPoint {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("ReservedSyncobjPoint")
|
|
.field("syncobj", &self.syncobj.id())
|
|
.field("point", &self.point)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum FdSync {
|
|
SyncFile(SyncFile),
|
|
Syncobj(Rc<ReservedSyncobjPoint>),
|
|
}
|
|
|
|
unsafe impl UnsafeCellCloneSafe for FdSync {}
|
|
|
|
impl FdSync {
|
|
pub async fn try_signaled(&self, ring: &Rc<IoUring>) -> Result<(), IoUringError> {
|
|
match self {
|
|
FdSync::SyncFile(f) => ring.readable(&f.0).await.map(drop),
|
|
FdSync::Syncobj(obj) => obj.signaled.signaled().await,
|
|
}
|
|
}
|
|
|
|
pub async fn signaled(&self, ring: &Rc<IoUring>, name: &str) {
|
|
if let Err(e) = self.try_signaled(ring).await {
|
|
log::error!(
|
|
"Could not wait for {name} sync to become signaled: {}",
|
|
ErrorFmt(e),
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn signaled_blocking(&self, name: &str) {
|
|
let res = match self {
|
|
FdSync::Syncobj(obj) => obj.signaled.signaled_blocking(),
|
|
FdSync::SyncFile(f) => {
|
|
let mut pollfd = c::pollfd {
|
|
fd: f.raw(),
|
|
events: c::POLLIN,
|
|
revents: 0,
|
|
};
|
|
uapi::poll(slice::from_mut(&mut pollfd), -1)
|
|
.map(drop)
|
|
.to_os_error()
|
|
}
|
|
};
|
|
if let Err(e) = res {
|
|
log::error!(
|
|
"Could not wait for {name} sync to become signaled: {}",
|
|
ErrorFmt(e),
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn is_unsignaled(&self) -> bool {
|
|
!self.is_signaled()
|
|
}
|
|
|
|
pub fn is_signaled(&self) -> bool {
|
|
match self {
|
|
FdSync::Syncobj(obj) => obj.signaled.is_signaled(),
|
|
FdSync::SyncFile(f) => {
|
|
let mut pollfd = c::pollfd {
|
|
fd: f.raw(),
|
|
events: c::POLLIN,
|
|
revents: 0,
|
|
};
|
|
uapi::poll(slice::from_mut(&mut pollfd), 0) == Ok(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn get_sync_file(&self) -> Option<&SyncFile> {
|
|
match self {
|
|
FdSync::SyncFile(f) => Some(f),
|
|
FdSync::Syncobj(obj) => {
|
|
if obj.signaled.is_signaled() {
|
|
return None;
|
|
}
|
|
obj.sync_file
|
|
.get_or_init(|| {
|
|
match obj.ctx.export_sync_file_blocking(&obj.syncobj, obj.point) {
|
|
Ok(sf) => Some(sf),
|
|
Err(e) => {
|
|
log::error!("Could not export sync file: {}", ErrorFmt(e));
|
|
None
|
|
}
|
|
}
|
|
})
|
|
.as_ref()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct DirectScanoutPosition {
|
|
pub src_width: i32,
|
|
pub src_height: i32,
|
|
pub crtc_x: i32,
|
|
pub crtc_y: i32,
|
|
pub crtc_width: i32,
|
|
pub crtc_height: i32,
|
|
}
|
|
|
|
impl GfxRenderPass {
|
|
pub fn prepare_direct_scanout(
|
|
&self,
|
|
mode_w: i32,
|
|
mode_h: i32,
|
|
blend_cd: &Rc<ColorDescription>,
|
|
cd: &Rc<ColorDescription>,
|
|
no_scaling: bool,
|
|
) -> Option<(&CopyTexture, DirectScanoutPosition)> {
|
|
let ct = 'ct: {
|
|
let mut ops = self.ops.iter().rev();
|
|
let ct = 'ct2: {
|
|
for opt in &mut ops {
|
|
match opt {
|
|
GfxApiOpt::Sync => {}
|
|
GfxApiOpt::FillRect(_) => {
|
|
// Top-most layer must be a texture.
|
|
return None;
|
|
}
|
|
GfxApiOpt::CopyTexture(ct) => break 'ct2 ct,
|
|
GfxApiOpt::RoundedFillRect(_) => return None,
|
|
GfxApiOpt::RoundedCopyTexture(_) => return None,
|
|
}
|
|
}
|
|
return None;
|
|
};
|
|
if ct.alpha_mode != AlphaMode::PremultipliedElectrical {
|
|
// Direct scanout requires premultiplied electrical alpha.
|
|
return None;
|
|
}
|
|
if !ct.cd.embeds_into(cd) {
|
|
// Direct scanout requires embeddable color descriptions.
|
|
return None;
|
|
}
|
|
if !ct.opaque && !ct.cd.embeds_into(blend_cd) {
|
|
// Blending changes the appearance of translucent buffers.
|
|
return None;
|
|
}
|
|
if ct.alpha.is_some() {
|
|
// Direct scanout with alpha factor is not supported.
|
|
return None;
|
|
}
|
|
if !ct.tex.format().has_alpha && ct.target.is_covering() {
|
|
// Texture covers the entire screen and is opaque.
|
|
break 'ct ct;
|
|
}
|
|
for opt in ops {
|
|
match opt {
|
|
GfxApiOpt::Sync => {}
|
|
GfxApiOpt::FillRect(fr) => {
|
|
if fr.effective_color() == Color::SOLID_BLACK {
|
|
// Black fills can be ignored because this is the CRTC background color.
|
|
if fr.rect.is_covering() {
|
|
// If fill covers the entire screen, we don't have to look further.
|
|
break 'ct ct;
|
|
}
|
|
} else {
|
|
// Fill could be visible.
|
|
return None;
|
|
}
|
|
}
|
|
GfxApiOpt::CopyTexture(_) => {
|
|
// Texture could be visible.
|
|
return None;
|
|
}
|
|
GfxApiOpt::RoundedFillRect(_) => return None,
|
|
GfxApiOpt::RoundedCopyTexture(_) => return None,
|
|
}
|
|
}
|
|
if let Some(clear) = self.clear
|
|
&& clear != Color::SOLID_BLACK
|
|
{
|
|
// Background could be visible.
|
|
return None;
|
|
}
|
|
ct
|
|
};
|
|
if let AcquireSync::None | AcquireSync::Implicit = ct.acquire_sync {
|
|
// Cannot perform scanout without explicit sync.
|
|
return None;
|
|
}
|
|
if ct.source.buffer_transform != ct.target.output_transform {
|
|
// Rotations and mirroring are not supported.
|
|
return None;
|
|
}
|
|
if !ct.source.is_covering() {
|
|
// Viewports are not supported.
|
|
return None;
|
|
}
|
|
if ct.target.x1 < -1.0 || ct.target.y1 < -1.0 || ct.target.x2 > 1.0 || ct.target.y2 > 1.0 {
|
|
// Rendering outside the screen is not supported.
|
|
return None;
|
|
}
|
|
let (tex_w, tex_h) = ct.tex.size();
|
|
let (x1, x2, y1, y2) = {
|
|
let plane_w = mode_w as f32;
|
|
let plane_h = mode_h as f32;
|
|
let ((x1, x2), (y1, y2)) = ct
|
|
.target
|
|
.output_transform
|
|
.maybe_swap(((ct.target.x1, ct.target.x2), (ct.target.y1, ct.target.y2)));
|
|
(
|
|
(x1 + 1.0) * plane_w / 2.0,
|
|
(x2 + 1.0) * plane_w / 2.0,
|
|
(y1 + 1.0) * plane_h / 2.0,
|
|
(y2 + 1.0) * plane_h / 2.0,
|
|
)
|
|
};
|
|
let (crtc_w, crtc_h) = (x2 - x1, y2 - y1);
|
|
if crtc_w < 0.0 || crtc_h < 0.0 {
|
|
// Flipping x or y axis is not supported.
|
|
return None;
|
|
}
|
|
if no_scaling && (tex_w as f32, tex_h as f32) != (crtc_w, crtc_h) {
|
|
// If scaling is not supported, we cannot scale the texture.
|
|
return None;
|
|
}
|
|
let position = DirectScanoutPosition {
|
|
src_width: tex_w,
|
|
src_height: tex_h,
|
|
crtc_x: x1 as _,
|
|
crtc_y: y1 as _,
|
|
crtc_width: crtc_w as _,
|
|
crtc_height: crtc_h as _,
|
|
};
|
|
Some((ct, position))
|
|
}
|
|
}
|