diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 96e9244d..bf3415b3 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -15,7 +15,7 @@ use { timer::Timer, video::{ connector_type::{ConnectorType, CON_UNKNOWN}, - Connector, DrmDevice, GfxApi, Mode, + Connector, DrmDevice, GfxApi, Mode, Transform, }, Axis, Direction, ModifiedKeySym, PciId, Workspace, }, @@ -473,6 +473,13 @@ impl Client { self.send(&ClientMessage::ConnectorSetEnabled { connector, enabled }); } + pub fn connector_set_transform(&self, connector: Connector, transform: Transform) { + self.send(&ClientMessage::ConnectorSetTransform { + connector, + transform, + }); + } + pub fn device_connectors(&self, device: DrmDevice) -> Vec { let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device }); get_response!(res, vec![], GetDeviceConnectors { connectors }); diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index fd32e7a7..49c69e81 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -5,7 +5,7 @@ use { logging::LogLevel, theme::{colors::Colorable, sized::Resizable, Color}, timer::Timer, - video::{connector_type::ConnectorType, Connector, DrmDevice, GfxApi}, + video::{connector_type::ConnectorType, Connector, DrmDevice, GfxApi, Transform}, Axis, Direction, PciId, Workspace, }, serde::{Deserialize, Serialize}, @@ -342,6 +342,10 @@ pub enum ClientMessage<'a> { device: Option, enabled: bool, }, + ConnectorSetTransform { + connector: Connector, + transform: Transform, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/video.rs b/jay-config/src/video.rs index 9152cd5d..92c4248e 100644 --- a/jay-config/src/video.rs +++ b/jay-config/src/video.rs @@ -158,6 +158,15 @@ impl Connector { } get!().connector_set_enabled(self, enabled); } + + /// Sets the transformation to apply to the content of this connector. + pub fn set_transform(self, transform: Transform) { + if !self.exists() { + log::warn!("set_transform called on a connector that does not exist"); + return; + } + get!().connector_set_transform(self, transform); + } } /// Returns all available DRM devices. @@ -403,3 +412,25 @@ pub fn set_gfx_api(gfx_api: GfxApi) { pub fn set_direct_scanout_enabled(enabled: bool) { get!().set_direct_scanout_enabled(None, enabled); } + +/// A transformation. +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] +pub enum Transform { + /// No transformation. + #[default] + None, + /// Rotate 90 degrees counter-clockwise. + Rotate90, + /// Rotate 180 degrees counter-clockwise. + Rotate180, + /// Rotate 270 degrees counter-clockwise. + Rotate270, + /// Flip around the vertical axis. + Flip, + /// Flip around the vertical axis, then rotate 90 degrees counter-clockwise. + FlipRotate90, + /// Flip around the vertical axis, then rotate 180 degrees counter-clockwise. + FlipRotate180, + /// Flip around the vertical axis, then rotate 270 degrees counter-clockwise. + FlipRotate270, +} diff --git a/src/backend.rs b/src/backend.rs index cdeed98e..45548c6a 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -100,7 +100,7 @@ pub trait HardwareCursor: Debug { fn set_position(&self, x: i32, y: i32); fn swap_buffer(&self); fn commit(&self); - fn max_size(&self) -> (i32, i32); + fn size(&self) -> (i32, i32); } pub type TransformMatrix = [[f64; 2]; 2]; diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index b33c9b0c..75b2ff3f 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -9,10 +9,7 @@ use { drm_feedback::DrmFeedback, edid::Descriptor, format::{Format, ARGB8888, XRGB8888}, - gfx_api::{ - AbsoluteRect, BufferPoints, GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, - GfxTexture, - }, + gfx_api::{GfxApiOpt, GfxContext, GfxFramebuffer, GfxRenderPass, GfxTexture}, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC}, renderer::RenderResult, state::State, @@ -23,6 +20,7 @@ use { asyncevent::AsyncEvent, bitflags::BitflagsExt, clonecell::CloneCell, copyhashmap::CopyHashMap, debug_fn::debug_fn, errorfmt::ErrorFmt, numcell::NumCell, opaque_cell::OpaqueCell, oserror::OsError, syncqueue::SyncQueue, + transform_ext::TransformExt, }, video::{ dmabuf::DmaBufId, @@ -287,7 +285,7 @@ impl HardwareCursor for MetalHardwareCursor { } } - fn max_size(&self) -> (i32, i32) { + fn size(&self) -> (i32, i32) { ( self.connector.dev.cursor_width as _, self.connector.dev.cursor_height as _, @@ -428,8 +426,6 @@ impl MetalConnector { pass: &GfxRenderPass, plane: &Rc, ) -> Option { - let plane_w = plane.mode_w.get(); - let plane_h = plane.mode_h.get(); let ct = 'ct: { let mut ops = pass.ops.iter().rev(); let ct = 'ct2: { @@ -445,13 +441,7 @@ impl MetalConnector { } return None; }; - let plane_rect = AbsoluteRect { - x1: 0.0, - x2: plane_w as f32, - y1: 0.0, - y2: plane_h as f32, - }; - if !ct.tex.format().has_alpha && ct.target == plane_rect { + if !ct.tex.format().has_alpha && ct.target.is_covering() { // Texture covers the entire screen and is opaque. break 'ct ct; } @@ -461,7 +451,7 @@ impl MetalConnector { GfxApiOpt::FillRect(fr) => { if fr.color == Color::SOLID_BLACK { // Black fills can be ignored because this is the CRTC background color. - if fr.rect == plane_rect { + if fr.rect.is_covering() { // If fill covers the entire screen, we don't have to look further. break 'ct ct; } @@ -484,20 +474,34 @@ impl MetalConnector { } ct }; - if ct.source != BufferPoints::identity() { - // Non-trivial transforms are not supported. + if ct.source.buffer_transform != ct.target.output_transform { + // Rotations and mirroring are not supported. return None; } - if ct.target.x1 < 0.0 - || ct.target.y1 < 0.0 - || ct.target.x2 > plane_w as f32 - || ct.target.y2 > plane_h as f32 - { + 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 (crtc_w, crtc_h) = (ct.target.x2 - ct.target.x1, ct.target.y2 - ct.target.y1); + let (x1, x2, y1, y2) = { + let plane_w = plane.mode_w.get() as f32; + let plane_h = plane.mode_h.get() 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; @@ -513,8 +517,8 @@ impl MetalConnector { let position = DirectScanoutPosition { src_width: tex_w, src_height: tex_h, - crtc_x: ct.target.x1 as _, - crtc_y: ct.target.y1 as _, + crtc_x: x1 as _, + crtc_y: y1 as _, crtc_width: crtc_w as _, crtc_height: crtc_h as _, }; @@ -595,9 +599,9 @@ impl MetalConnector { output.global.preferred_scale.get(), render_hw_cursor, output.has_fullscreen(), + output.global.transform.get(), ); let try_direct_scanout = try_direct_scanout - && !output.global.have_shm_screencopies() && self.direct_scanout_enabled() // at least on AMD, using a FB on a different device for rendering will fail // and destroy the render context. it's possible to work around this by waiting @@ -609,7 +613,6 @@ impl MetalConnector { if try_direct_scanout { if let Some(dsd) = self.prepare_direct_scanout(&pass, plane) { output.perform_screencopies( - None, &dsd.tex, !render_hw_cursor, dsd.position.crtc_x, @@ -634,14 +637,7 @@ impl MetalConnector { if let Some(tex) = &buffer.dev_tex { buffer.dev_fb.copy_texture(tex, 0, 0); } - output.perform_screencopies( - Some(&*buffer_fb), - &buffer.render_tex, - !render_hw_cursor, - 0, - 0, - None, - ); + output.perform_screencopies(&buffer.render_tex, !render_hw_cursor, 0, 0, None); buffer.drm.clone() } Some(dsd) => dsd.fb.clone(), @@ -729,7 +725,7 @@ impl MetalConnector { buffer.dev_fb.copy_texture(tex, 0, 0); } } - let (width, height) = buffer.dev_fb.size(); + let (width, height) = buffer.dev_fb.physical_size(); changes.change_object(plane.id, |c| { c.change(plane.fb_id, buffer.drm.id().0 as _); c.change(plane.crtc_id.id, crtc.id.0 as _); diff --git a/src/compositor.rs b/src/compositor.rs index 65249213..ee462330 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -204,6 +204,7 @@ fn start_compositor2( dma_buf_ids: Default::default(), drm_feedback_ids: Default::default(), direct_scanout_enabled: Cell::new(true), + output_transforms: Default::default(), }); state.tracker.register(ClientId::from_raw(0)); create_dummy_output(&state); diff --git a/src/config/handler.rs b/src/config/handler.rs index 9ba8708a..42a078a7 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -40,7 +40,7 @@ use { logging::LogLevel, theme::{colors::Colorable, sized::Resizable}, timer::Timer as JayTimer, - video::{Connector, DrmDevice, GfxApi}, + video::{Connector, DrmDevice, GfxApi, Transform}, Axis, Direction, Workspace, }, libloading::Library, @@ -751,6 +751,17 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_connector_set_transform( + &self, + connector: Connector, + transform: Transform, + ) -> Result<(), CphError> { + let connector = self.get_output(connector)?; + connector.node.update_transform(transform); + self.state.damage(); + Ok(()) + } + fn handle_connector_set_position( &self, connector: Connector, @@ -1338,6 +1349,12 @@ impl ConfigProxyHandler { ClientMessage::SetDirectScanoutEnabled { device, enabled } => self .handle_set_direct_scanout_enabled(device, enabled) .wrn("set_direct_scanout_enabled")?, + ClientMessage::ConnectorSetTransform { + connector, + transform, + } => self + .handle_connector_set_transform(connector, transform) + .wrn("connector_set_transform")?, } Ok(()) } diff --git a/src/cursor.rs b/src/cursor.rs index 7aeb711f..afcab31b 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -358,7 +358,7 @@ fn render_img(image: &InstantiatedCursorImage, renderer: &mut Renderer, x: Fixed } else { img.extents.move_(x.round_down(), y.round_down()) }; - if extents.intersects(&renderer.physical_extents()) { + if extents.intersects(&renderer.pixel_extents()) { renderer.base.render_texture( &img.tex, extents.x1(), diff --git a/src/format.rs b/src/format.rs index 6cec1995..f8594d25 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,6 +1,6 @@ use { crate::{ - gfx_apis::gl::sys::{GLint, GL_BGRA_EXT, GL_RGBA, GL_UNSIGNED_BYTE}, + gfx_apis::gl::sys::{GLenum, GLint, GL_BGRA_EXT, GL_RGBA, GL_RGBA8, GL_UNSIGNED_BYTE}, pipewire::pw_pod::{ SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, SpaVideoFormat, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, @@ -18,6 +18,7 @@ pub struct Format { pub name: &'static str, pub bpp: u32, pub gl_format: GLint, + pub gl_internal_format: GLenum, pub gl_type: GLint, pub vk_format: vk::Format, pub drm: u32, @@ -92,6 +93,7 @@ pub static ARGB8888: &Format = &Format { name: "argb8888", bpp: 4, gl_format: GL_BGRA_EXT, + gl_internal_format: GL_RGBA8, gl_type: GL_UNSIGNED_BYTE, vk_format: vk::Format::B8G8R8A8_UNORM, drm: ARGB8888_DRM, @@ -107,6 +109,7 @@ pub static XRGB8888: &Format = &Format { name: "xrgb8888", bpp: 4, gl_format: GL_BGRA_EXT, + gl_internal_format: GL_RGBA8, gl_type: GL_UNSIGNED_BYTE, vk_format: vk::Format::B8G8R8A8_UNORM, drm: XRGB8888_DRM, @@ -122,6 +125,7 @@ static ABGR8888: &Format = &Format { name: "abgr8888", bpp: 4, gl_format: GL_RGBA, + gl_internal_format: GL_RGBA8, gl_type: GL_UNSIGNED_BYTE, vk_format: vk::Format::R8G8B8A8_UNORM, drm: fourcc_code('A', 'B', '2', '4'), @@ -137,6 +141,7 @@ static XBGR8888: &Format = &Format { name: "xbgr8888", bpp: 4, gl_format: GL_RGBA, + gl_internal_format: GL_RGBA8, gl_type: GL_UNSIGNED_BYTE, vk_format: vk::Format::R8G8B8A8_UNORM, drm: fourcc_code('X', 'B', '2', '4'), diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 26c3ae2f..0672a0dd 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -9,12 +9,12 @@ use { state::State, theme::Color, tree::{Node, OutputNode}, - utils::numcell::NumCell, + utils::{numcell::NumCell, transform_ext::TransformExt}, video::{dmabuf::DmaBuf, gbm::GbmDevice, Modifier}, }, ahash::AHashMap, indexmap::IndexSet, - jay_config::video::GfxApi, + jay_config::video::{GfxApi, Transform}, std::{ any::Any, cell::Cell, @@ -38,98 +38,109 @@ pub struct GfxRenderPass { } #[derive(Default, Debug, Copy, Clone, PartialEq)] -pub struct BufferPoint { - pub x: f32, - pub y: f32, +pub struct SampleRect { + pub x1: f32, + pub y1: f32, + pub x2: f32, + pub y2: f32, + pub buffer_transform: Transform, } -impl BufferPoint { - pub fn is_leq_1(&self) -> bool { - self.x <= 1.0 && self.y <= 1.0 - } - - pub fn top_left() -> Self { - Self { x: 0.0, y: 0.0 } - } - - pub fn top_right() -> Self { - Self { x: 1.0, y: 0.0 } +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 bottom_left() -> Self { - Self { x: 0.0, y: 1.0 } + pub fn is_covering(&self) -> bool { + self.x1 == 0.0 && self.y1 == 0.0 && self.x2 == 1.0 && self.y2 == 1.0 } - pub fn bottom_right() -> Self { - Self { x: 1.0, y: 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, x1], [y1, x2], [y2, x1], [y2, x2]], + Rotate180 => [[x1, y2], [x2, y2], [x1, y1], [x2, y1]], + Rotate270 => [[y2, x2], [y2, x1], [y1, x2], [y1, x1]], + Flip => [[x1, y1], [x2, y1], [x1, y2], [x2, y2]], + FlipRotate90 => [[y1, x2], [y1, x1], [y2, x2], [y2, x1]], + FlipRotate180 => [[x2, y2], [x1, y2], [x2, y1], [x1, y1]], + FlipRotate270 => [[y2, x1], [y2, x2], [y1, x1], [y1, x2]], + } } } -#[derive(Default, Debug, Copy, Clone, PartialEq)] -pub struct BufferPoints { - pub top_left: BufferPoint, - pub top_right: BufferPoint, - pub bottom_left: BufferPoint, - pub bottom_right: BufferPoint, +#[derive(Debug, PartialEq)] +pub struct FramebufferRect { + pub x1: f32, + pub x2: f32, + pub y1: f32, + pub y2: f32, + pub output_transform: Transform, } -impl BufferPoints { - pub fn norm(&self, width: f32, height: f32) -> Self { +impl FramebufferRect { + pub fn new( + x1: f32, + y1: f32, + x2: f32, + y2: f32, + transform: Transform, + width: f32, + height: f32, + ) -> Self { Self { - top_left: BufferPoint { - x: self.top_left.x / width, - y: self.top_left.y / height, - }, - top_right: BufferPoint { - x: self.top_right.x / width, - y: self.top_right.y / height, - }, - bottom_left: BufferPoint { - x: self.bottom_left.x / width, - y: self.bottom_left.y / height, - }, - bottom_right: BufferPoint { - x: self.bottom_right.x / width, - y: self.bottom_right.y / height, - }, + 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 is_leq_1(&self) -> bool { - self.top_left.is_leq_1() - && self.top_right.is_leq_1() - && self.bottom_left.is_leq_1() - && self.bottom_right.is_leq_1() - } - - pub fn identity() -> Self { - Self { - top_left: BufferPoint::top_left(), - top_right: BufferPoint::top_right(), - bottom_left: BufferPoint::bottom_left(), - bottom_right: BufferPoint::bottom_right(), + 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]], } } -} -#[derive(Debug, PartialEq)] -pub struct AbsoluteRect { - pub x1: f32, - pub x2: f32, - pub y1: f32, - pub y2: f32, + pub fn is_covering(&self) -> bool { + self.x1 == -1.0 && self.y1 == -1.0 && self.x2 == 1.0 && self.y2 == 1.0 + } } #[derive(Debug)] pub struct FillRect { - pub rect: AbsoluteRect, + pub rect: FramebufferRect, pub color: Color, } pub struct CopyTexture { pub tex: Rc, - pub source: BufferPoints, - pub target: AbsoluteRect, + pub source: SampleRect, + pub target: FramebufferRect, } #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -145,17 +156,18 @@ pub trait GfxFramebuffer: Debug { fn take_render_ops(&self) -> Vec; - fn size(&self) -> (i32, i32); + fn physical_size(&self) -> (i32, i32); fn render(&self, ops: Vec, clear: Option<&Color>); fn copy_to_shm( - &self, + self: Rc, x: i32, y: i32, width: i32, height: i32, - format: &Format, + stride: i32, + format: &'static Format, shm: &[Cell], ) -> Result<(), GfxError>; @@ -172,15 +184,32 @@ impl dyn GfxFramebuffer { self.render(ops, Some(&Color { r, g, b, a })); } + pub fn logical_size(&self, transform: Transform) -> (i32, i32) { + transform.maybe_swap(self.physical_size()) + } + + pub fn renderer_base<'a>( + &self, + ops: &'a mut Vec, + scale: Scale, + transform: Transform, + ) -> RendererBase<'a> { + let (width, height) = self.logical_size(transform); + RendererBase { + ops, + scaled: scale != 1, + scale, + scalef: scale.to_f64(), + transform, + fb_width: width as _, + fb_height: height as _, + } + } + pub fn copy_texture(&self, texture: &Rc, x: i32, y: i32) { let mut ops = self.take_render_ops(); let scale = Scale::from_int(1); - let mut renderer = RendererBase { - ops: &mut ops, - scaled: false, - scale, - scalef: 1.0, - }; + let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); renderer.render_texture(texture, x, y, None, None, scale, None); let clear = self.format().has_alpha.then_some(&Color::TRANSPARENT); self.render(ops, clear); @@ -193,12 +222,7 @@ impl dyn GfxFramebuffer { f: &mut dyn FnMut(&mut RendererBase), ) { let mut ops = self.take_render_ops(); - let mut renderer = RendererBase { - ops: &mut ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), - }; + let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); f(&mut renderer); self.render(ops, clear); } @@ -212,20 +236,18 @@ impl dyn GfxFramebuffer { scale: Scale, render_hardware_cursor: bool, black_background: bool, + transform: Transform, ) -> GfxRenderPass { let mut ops = self.take_render_ops(); - let (width, height) = self.size(); let mut renderer = Renderer { - base: RendererBase { - ops: &mut ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), - }, + base: self.renderer_base(&mut ops, scale, transform), state, result, logical_extents: node.node_absolute_position().at_point(0, 0), - physical_extents: Rect::new(0, 0, width, height).unwrap(), + pixel_extents: { + let (width, height) = self.logical_size(transform); + Rect::new(0, 0, width, height).unwrap() + }, }; node.node_render(&mut renderer, 0, 0, None); if let Some(rect) = cursor_rect { @@ -283,6 +305,7 @@ impl dyn GfxFramebuffer { scale, render_hardware_cursor, node.has_fullscreen(), + node.global.transform.get(), ) } @@ -295,6 +318,7 @@ impl dyn GfxFramebuffer { scale: Scale, render_hardware_cursor: bool, black_background: bool, + transform: Transform, ) { let pass = self.create_render_pass( node, @@ -304,24 +328,28 @@ impl dyn GfxFramebuffer { scale, render_hardware_cursor, black_background, + transform, ); self.perform_render_pass(pass); } - pub fn render_hardware_cursor(&self, cursor: &dyn Cursor, state: &State, scale: Scale) { + pub fn render_hardware_cursor( + &self, + cursor: &dyn Cursor, + state: &State, + scale: Scale, + transform: Transform, + ) { let mut ops = self.take_render_ops(); - let (width, height) = self.size(); let mut renderer = Renderer { - base: RendererBase { - ops: &mut ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), - }, + base: self.renderer_base(&mut ops, scale, transform), state, result: None, logical_extents: Rect::new_empty(0, 0), - physical_extents: Rect::new(0, 0, width, height).unwrap(), + pixel_extents: { + let (width, height) = self.logical_size(transform); + Rect::new(0, 0, width, height).unwrap() + }, }; cursor.render_hardware_cursor(&mut renderer); self.render(ops, Some(&Color::TRANSPARENT)); @@ -414,6 +442,14 @@ pub trait GfxContext: Debug { fn gbm(&self) -> &GbmDevice; fn gfx_api(&self) -> GfxApi; + + fn create_fb( + self: Rc, + width: i32, + height: i32, + stride: i32, + format: &'static Format, + ) -> Result, GfxError>; } #[derive(Debug)] diff --git a/src/gfx_apis/gl.rs b/src/gfx_apis/gl.rs index 908f12d9..ecfa58fc 100644 --- a/src/gfx_apis/gl.rs +++ b/src/gfx_apis/gl.rs @@ -68,7 +68,8 @@ macro_rules! dynload { use { crate::{ gfx_api::{ - BufferPoints, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, + CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxContext, GfxError, GfxTexture, + SampleRect, }, gfx_apis::gl::{ gl::texture::image_target, @@ -178,13 +179,13 @@ enum RenderError { ExternalUnsupported, #[error("OpenGL context does not support any formats")] NoSupportedFormats, - #[error("Unsupported operation")] - UnsupportedOperation, + #[error("Cannot convert a shm texture into a framebuffer")] + ShmTextureToFb, } #[derive(Default)] struct GfxGlState { - triangles: RefCell>, + triangles: RefCell>, fill_rect: VecStorage<&'static FillRect>, copy_tex: VecStorage<&'static CopyTexture>, } @@ -198,8 +199,6 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) { let copy_tex = &mut *copy_tex; let mut triangles = state.triangles.borrow_mut(); let triangles = &mut *triangles; - let width = fb.gl.width as f32; - let height = fb.gl.height as f32; let mut i = 0; while i < ops.len() { macro_rules! has_ops { @@ -240,19 +239,14 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) { Some(c) if c == fr.color => {} _ => break, } - let x1 = 2.0 * (fr.rect.x1 / width) - 1.0; - let x2 = 2.0 * (fr.rect.x2 / width) - 1.0; - let y1 = 2.0 * (fr.rect.y1 / height) - 1.0; - let y2 = 2.0 * (fr.rect.y2 / height) - 1.0; + let [top_right, top_left, bottom_right, bottom_left] = fr.rect.to_points(); triangles.extend_from_slice(&[ - // triangle 1 - x2, y1, // top right - x1, y1, // top left - x1, y2, // bottom left - // triangle 2 - x2, y1, // top right - x1, y2, // bottom left - x2, y2, // bottom right + top_right, + top_left, + bottom_left, + top_right, + bottom_left, + bottom_right, ]); i += 1; } @@ -262,16 +256,12 @@ fn run_ops(fb: &Framebuffer, ops: &[GfxApiOpt]) { } } for tex in &*copy_tex { - let x1 = 2.0 * (tex.target.x1 / width) - 1.0; - let y1 = 2.0 * (tex.target.y1 / height) - 1.0; - let x2 = 2.0 * (tex.target.x2 / width) - 1.0; - let y2 = 2.0 * (tex.target.y2 / height) - 1.0; - render_texture(&fb.ctx, &tex.tex.as_gl(), x1, y1, x2, y2, &tex.source) + render_texture(&fb.ctx, &tex.tex.as_gl(), &tex.target, &tex.source) } } } -fn fill_boxes3(ctx: &GlRenderContext, boxes: &[f32], color: &Color) { +fn fill_boxes3(ctx: &GlRenderContext, boxes: &[[f32; 2]], color: &Color) { let gles = ctx.ctx.dpy.gles; unsafe { (gles.glUseProgram)(ctx.fill_prog.prog); @@ -285,7 +275,7 @@ fn fill_boxes3(ctx: &GlRenderContext, boxes: &[f32], color: &Color) { boxes.as_ptr() as _, ); (gles.glEnableVertexAttribArray)(ctx.fill_prog_pos as _); - (gles.glDrawArrays)(GL_TRIANGLES, 0, (boxes.len() / 2) as _); + (gles.glDrawArrays)(GL_TRIANGLES, 0, boxes.len() as _); (gles.glDisableVertexAttribArray)(ctx.fill_prog_pos as _); } } @@ -293,11 +283,8 @@ fn fill_boxes3(ctx: &GlRenderContext, boxes: &[f32], color: &Color) { fn render_texture( ctx: &GlRenderContext, texture: &Texture, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - src: &BufferPoints, + target_rect: &FramebufferRect, + src: &SampleRect, ) { assert!(rc_eq(&ctx.ctx, &texture.ctx.ctx)); let gles = ctx.ctx.dpy.gles; @@ -334,23 +321,8 @@ fn render_texture( (gles.glUniform1i)(prog.tex, 0); - let texcoord = [ - src.top_right.x, - src.top_right.y, - src.top_left.x, - src.top_left.y, - src.bottom_right.x, - src.bottom_right.y, - src.bottom_left.x, - src.bottom_left.y, - ]; - - let pos = [ - x2, y1, // top right - x1, y1, // top left - x2, y2, // bottom right - x1, y2, // bottom left - ]; + let texcoord = src.to_points(); + let pos = target_rect.to_points(); (gles.glVertexAttribPointer)( prog.texcoord as _, diff --git a/src/gfx_apis/gl/gl/render_buffer.rs b/src/gfx_apis/gl/gl/render_buffer.rs index ed170326..db15c6f9 100644 --- a/src/gfx_apis/gl/gl/render_buffer.rs +++ b/src/gfx_apis/gl/gl/render_buffer.rs @@ -1,25 +1,53 @@ use { - crate::gfx_apis::gl::{ - egl::{context::EglContext, image::EglImage}, - gl::{ - frame_buffer::GlFrameBuffer, - sys::{ - GLeglImageOES, GLuint, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER, - GL_FRAMEBUFFER_COMPLETE, GL_RENDERBUFFER, + crate::{ + format::Format, + gfx_apis::gl::{ + egl::{context::EglContext, image::EglImage}, + gl::{ + frame_buffer::GlFrameBuffer, + sys::{ + GLeglImageOES, GLuint, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER, + GL_FRAMEBUFFER_COMPLETE, GL_RENDERBUFFER, + }, }, + RenderError, }, - RenderError, }, std::rc::Rc, }; pub struct GlRenderBuffer { - pub img: Rc, + pub _img: Option>, pub ctx: Rc, + pub width: i32, + pub height: i32, + pub format: &'static Format, rbo: GLuint, } impl GlRenderBuffer { + pub(in crate::gfx_apis::gl) unsafe fn new( + ctx: &Rc, + width: i32, + height: i32, + format: &'static Format, + ) -> Result, RenderError> { + let gles = &ctx.dpy.gles; + let mut rbo = 0; + (gles.glGenRenderbuffers)(1, &mut rbo); + (gles.glBindRenderbuffer)(GL_RENDERBUFFER, rbo); + (gles.glRenderbufferStorage)(GL_RENDERBUFFER, format.gl_internal_format, width, height); + (gles.glBindRenderbuffer)(GL_RENDERBUFFER, 0); + Ok(Rc::new(GlRenderBuffer { + _img: None, + ctx: ctx.clone(), + width, + height, + format, + rbo, + })) + } + pub(in crate::gfx_apis::gl) unsafe fn from_image( img: &Rc, ctx: &Rc, @@ -36,8 +64,11 @@ impl GlRenderBuffer { .glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, GLeglImageOES(img.img.0)); (gles.glBindRenderbuffer)(GL_RENDERBUFFER, 0); Ok(Rc::new(GlRenderBuffer { - img: img.clone(), + _img: Some(img.clone()), ctx: ctx.clone(), + width: img.dmabuf.width, + height: img.dmabuf.height, + format: img.dmabuf.format, rbo, })) } @@ -62,8 +93,8 @@ impl GlRenderBuffer { _tex: None, ctx: self.ctx.clone(), fbo, - width: self.img.dmabuf.width, - height: self.img.dmabuf.height, + width: self.width, + height: self.height, }; if status != GL_FRAMEBUFFER_COMPLETE { return Err(RenderError::CreateFramebuffer); diff --git a/src/gfx_apis/gl/gl/sys.rs b/src/gfx_apis/gl/gl/sys.rs index f1fcbb40..afbaec72 100644 --- a/src/gfx_apis/gl/gl/sys.rs +++ b/src/gfx_apis/gl/gl/sys.rs @@ -14,6 +14,7 @@ pub type GLuint = c::c_uint; egl_transparent!(GLeglImageOES); pub const GL_RGBA: GLint = 0x1908; +pub const GL_RGBA8: GLenum = 0x8058; pub const GL_BGRA_EXT: GLint = 0x80E1; pub const GL_CLAMP_TO_EDGE: GLint = 0x812F; pub const GL_COLOR_ATTACHMENT0: GLenum = 0x8CE0; @@ -49,6 +50,7 @@ dynload! { GLESV2: GlesV2 from "libGLESv2.so" { glGetString: unsafe fn(name: GLenum) -> *const u8, glGenRenderbuffers: unsafe fn(n: GLsizei, renderbuffers: *mut GLuint), + glRenderbufferStorage: unsafe fn(target: GLenum, format: GLenum, width: GLsizei, height: GLsizei), glDeleteRenderbuffers: unsafe fn(n: GLsizei, renderbuffers: *const GLuint), glBindRenderbuffer: unsafe fn(target: GLenum, renderbuffer: GLuint), glGenFramebuffers: unsafe fn(n: GLsizei, framebuffers: *mut GLuint), diff --git a/src/gfx_apis/gl/renderer/context.rs b/src/gfx_apis/gl/renderer/context.rs index b77cd4c1..b019e555 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -6,7 +6,7 @@ use { ResetStatus, }, gfx_apis::gl::{ - egl::{context::EglContext, display::EglDisplay}, + egl::{context::EglContext, display::EglDisplay, image::EglImage}, ext::GL_OES_EGL_IMAGE_EXTERNAL, gl::{ program::GlProgram, render_buffer::GlRenderBuffer, sys::GLint, texture::GlTexture, @@ -190,6 +190,20 @@ impl GlRenderContext { format, })) } + + pub fn image_to_fb( + self: &Rc, + img: &Rc, + ) -> Result, RenderError> { + self.ctx.with_current(|| unsafe { + let rb = GlRenderBuffer::from_image(img, &self.ctx)?; + let fb = rb.create_framebuffer()?; + Ok(Rc::new(Framebuffer { + ctx: self.clone(), + gl: fb, + })) + }) + } } impl GfxContext for GlRenderContext { @@ -241,4 +255,17 @@ impl GfxContext for GlRenderContext { fn gfx_api(&self) -> GfxApi { GfxApi::OpenGl } + + fn create_fb( + self: Rc, + width: i32, + height: i32, + _stride: i32, + format: &'static Format, + ) -> Result, GfxError> { + let fb = self.ctx.with_current(|| unsafe { + GlRenderBuffer::new(&self.ctx, width, height, format)?.create_framebuffer() + })?; + Ok(Rc::new(Framebuffer { ctx: self, gl: fb })) + } } diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 3be56f86..427f71f1 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -97,7 +97,7 @@ impl GfxFramebuffer for Framebuffer { ops } - fn size(&self) -> (i32, i32) { + fn physical_size(&self) -> (i32, i32) { (self.gl.width, self.gl.height) } @@ -106,19 +106,20 @@ impl GfxFramebuffer for Framebuffer { } fn copy_to_shm( - &self, + self: Rc, x: i32, y: i32, width: i32, height: i32, - format: &Format, + _stride: i32, + format: &'static Format, shm: &[Cell], ) -> Result<(), GfxError> { - self.copy_to_shm(x, y, width, height, format, shm); + (*self).copy_to_shm(x, y, width, height, format, shm); Ok(()) } fn format(&self) -> &'static Format { - self.gl.rb.img.dmabuf.format + self.gl.rb.format } } diff --git a/src/gfx_apis/gl/renderer/image.rs b/src/gfx_apis/gl/renderer/image.rs index 50295260..4044206d 100644 --- a/src/gfx_apis/gl/renderer/image.rs +++ b/src/gfx_apis/gl/renderer/image.rs @@ -2,9 +2,8 @@ use { crate::{ gfx_api::{GfxError, GfxFramebuffer, GfxImage, GfxTexture}, gfx_apis::gl::{ - egl::image::EglImage, - gl::{render_buffer::GlRenderBuffer, texture::GlTexture}, - Framebuffer, GlRenderContext, RenderError, Texture, + egl::image::EglImage, gl::texture::GlTexture, Framebuffer, GlRenderContext, + RenderError, Texture, }, }, std::rc::Rc, @@ -34,14 +33,7 @@ impl Image { } fn to_framebuffer(&self) -> Result, RenderError> { - self.ctx.ctx.with_current(|| unsafe { - let rb = GlRenderBuffer::from_image(&self.gl, &self.ctx.ctx)?; - let fb = rb.create_framebuffer()?; - Ok(Rc::new(Framebuffer { - ctx: self.ctx.clone(), - gl: fb, - })) - }) + self.ctx.image_to_fb(&self.gl) } } diff --git a/src/gfx_apis/gl/renderer/texture.rs b/src/gfx_apis/gl/renderer/texture.rs index c2d479d5..efe2b029 100644 --- a/src/gfx_apis/gl/renderer/texture.rs +++ b/src/gfx_apis/gl/renderer/texture.rs @@ -2,7 +2,11 @@ use { crate::{ format::Format, gfx_api::{GfxError, GfxTexture, TextureReservations}, - gfx_apis::gl::{gl::texture::GlTexture, renderer::context::GlRenderContext, RenderError}, + gfx_apis::gl::{ + gl::texture::GlTexture, + renderer::{context::GlRenderContext, framebuffer::Framebuffer}, + RenderError, + }, video::dmabuf::DmaBuf, }, std::{ @@ -34,6 +38,13 @@ impl Texture { pub fn height(&self) -> i32 { self.gl.height } + + pub fn to_framebuffer(&self) -> Result, RenderError> { + match &self.gl.img { + Some(img) => self.ctx.image_to_fb(img), + _ => Err(RenderError::ShmTextureToFb), + } + } } impl GfxTexture for Texture { @@ -51,15 +62,17 @@ impl GfxTexture for Texture { fn read_pixels( self: Rc, - _x: i32, - _y: i32, - _width: i32, - _height: i32, + x: i32, + y: i32, + width: i32, + height: i32, _stride: i32, - _format: &Format, - _shm: &[Cell], + format: &Format, + shm: &[Cell], ) -> Result<(), GfxError> { - Err(RenderError::UnsupportedOperation.into()) + self.to_framebuffer()? + .copy_to_shm(x, y, width, height, format, shm); + Ok(()) } fn dmabuf(&self) -> Option<&DmaBuf> { diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 5dfc8e91..9cf7c7b2 100644 --- a/src/gfx_apis/vulkan.rs +++ b/src/gfx_apis/vulkan.rs @@ -18,7 +18,9 @@ use { crate::{ async_engine::AsyncEngine, format::Format, - gfx_api::{GfxContext, GfxError, GfxFormat, GfxImage, GfxTexture, ResetStatus}, + gfx_api::{ + GfxContext, GfxError, GfxFormat, GfxFramebuffer, GfxImage, GfxTexture, ResetStatus, + }, gfx_apis::vulkan::{ image::VulkanImageMemory, instance::VulkanInstance, renderer::VulkanRenderer, }, @@ -173,8 +175,6 @@ pub enum VulkanError { height: i32, stride: i32, }, - #[error("Unsupported operation")] - UnsupportedOperation, } impl From for GfxError { @@ -257,6 +257,19 @@ impl GfxContext for Context { fn gfx_api(&self) -> GfxApi { GfxApi::Vulkan } + + fn create_fb( + self: Rc, + width: i32, + height: i32, + stride: i32, + format: &'static Format, + ) -> Result, GfxError> { + let fb = self + .0 + .create_shm_texture(format, width, height, stride, &[], true)?; + Ok(fb) + } } impl Drop for Context { diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 6754133e..63f773a5 100644 --- a/src/gfx_apis/vulkan/image.rs +++ b/src/gfx_apis/vulkan/image.rs @@ -524,7 +524,7 @@ impl GfxFramebuffer for VulkanImage { self.render_ops.take() } - fn size(&self) -> (i32, i32) { + fn physical_size(&self) -> (i32, i32) { (self.width as _, self.height as _) } @@ -533,15 +533,18 @@ impl GfxFramebuffer for VulkanImage { } fn copy_to_shm( - &self, - _x: i32, - _y: i32, - _width: i32, - _height: i32, - _format: &Format, - _shm: &[Cell], + self: Rc, + x: i32, + y: i32, + width: i32, + height: i32, + stride: i32, + format: &'static Format, + shm: &[Cell], ) -> Result<(), GfxError> { - return Err(VulkanError::UnsupportedOperation.into()); + self.renderer + .read_pixels(&self, x, y, width, height, stride, format, shm) + .map_err(|e| e.into()) } fn format(&self) -> &'static Format { diff --git a/src/gfx_apis/vulkan/pipeline.rs b/src/gfx_apis/vulkan/pipeline.rs index cd30ad92..cd309581 100644 --- a/src/gfx_apis/vulkan/pipeline.rs +++ b/src/gfx_apis/vulkan/pipeline.rs @@ -107,7 +107,7 @@ impl VulkanDevice { let vertex_input_state = PipelineVertexInputStateCreateInfo::builder(); let rasterization_state = PipelineRasterizationStateCreateInfo::builder() .polygon_mode(PolygonMode::FILL) - .cull_mode(CullModeFlags::BACK) + .cull_mode(CullModeFlags::NONE) .line_width(1.0) .front_face(FrontFace::COUNTER_CLOCKWISE); let multisampling_state = PipelineMultisampleStateCreateInfo::builder() diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index bd5d418e..8c2f6c2c 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -2,10 +2,7 @@ use { crate::{ async_engine::SpawnedFuture, format::Format, - gfx_api::{ - AbsoluteRect, BufferPoint, BufferPoints, GfxApiOpt, GfxFormat, GfxFramebuffer, - GfxTexture, - }, + gfx_api::{GfxApiOpt, GfxFormat, GfxFramebuffer, GfxTexture}, gfx_apis::vulkan::{ allocator::VulkanAllocator, command::{VulkanCommandBuffer, VulkanCommandPool}, @@ -386,12 +383,7 @@ impl VulkanRenderer { } } - fn record_draws( - &self, - buf: CommandBuffer, - fb: &VulkanImage, - opts: &[GfxApiOpt], - ) -> Result<(), VulkanError> { + fn record_draws(&self, buf: CommandBuffer, opts: &[GfxApiOpt]) -> Result<(), VulkanError> { let dev = &self.device.device; let mut current_pipeline = None; let mut bind = |pipeline: &VulkanPipeline| { @@ -402,15 +394,13 @@ impl VulkanRenderer { } } }; - let width = fb.width as f32; - let height = fb.height as f32; for opt in opts { match opt { GfxApiOpt::Sync => {} GfxApiOpt::FillRect(r) => { bind(&self.fill_pipeline); let vert = FillVertPushConstants { - pos: r.rect.to_vk(width, height), + pos: r.rect.to_points(), }; let frag = FillFragPushConstants { color: r.color.to_array_srgb(), @@ -437,8 +427,8 @@ impl VulkanRenderer { let tex = c.tex.as_vk(&self.device.device); bind(&self.tex_pipeline); let vert = TexVertPushConstants { - pos: c.target.to_vk(width, height), - tex_pos: c.source.to_vk(), + pos: c.target.to_points(), + tex_pos: c.source.to_points(), }; let image_info = DescriptorImageInfo::builder() .image_view(tex.texture_view) @@ -879,7 +869,7 @@ impl VulkanRenderer { self.secondary_barriers(buf.buffer); self.begin_rendering(buf.buffer, fb, clear); self.set_viewport(buf.buffer, fb); - self.record_draws(buf.buffer, fb, opts)?; + self.record_draws(buf.buffer, opts)?; self.end_rendering(buf.buffer); self.final_barriers(buf.buffer, fb); self.end_command_buffer(buf.buffer)?; @@ -952,33 +942,6 @@ impl dyn GfxTexture { } } -impl AbsoluteRect { - fn to_vk(&self, width: f32, height: f32) -> [[f32; 2]; 4] { - let x1 = 2.0 * self.x1 / width - 1.0; - let x2 = 2.0 * self.x2 / width - 1.0; - let y1 = 2.0 * self.y1 / height - 1.0; - let y2 = 2.0 * self.y2 / height - 1.0; - [[x2, y1], [x1, y1], [x2, y2], [x1, y2]] - } -} - -impl BufferPoint { - fn to_vk(&self) -> [f32; 2] { - [self.x, self.y] - } -} - -impl BufferPoints { - fn to_vk(&self) -> [[f32; 2]; 4] { - [ - self.top_right.to_vk(), - self.top_left.to_vk(), - self.bottom_right.to_vk(), - self.bottom_left.to_vk(), - ] - } -} - fn image_barrier() -> ImageMemoryBarrier2Builder<'static> { ImageMemoryBarrier2::builder().subresource_range( ImageSubresourceRange::builder() diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index dcd01b69..77541294 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -176,12 +176,12 @@ impl JayScreencast { self.client.state.perform_screencopy( texture, &buffer.fb, - on.global.preferred_scale.get(), on.global.pos.get(), render_hardware_cursors, x_off, y_off, size, + on.global.transform.get(), ); self.client.event(Ready { self_id: self.id, @@ -218,7 +218,7 @@ impl JayScreencast { _ => return Err(JayScreencastError::XRGB8888), }; if let Some(output) = self.output.get() { - let mode = output.global.mode.get(); + let (width, height) = output.global.pixel_size(); let num = 3; for _ in 0..num { let mut usage = GBM_BO_USE_RENDERING; @@ -242,8 +242,8 @@ impl JayScreencast { }; let buffer = ctx.gbm().create_bo( &self.client.state.dma_buf_ids, - mode.width, - mode.height, + width, + height, XRGB8888, modifiers, usage, @@ -493,10 +493,7 @@ efrom!(JayScreencastError, ClientError); fn output_size(output: &Option>) -> (i32, i32) { match output { - Some(o) => { - let mode = o.global.mode.get(); - (mode.width, mode.height) - } + Some(o) => o.global.pixel_size(), _ => (0, 0), } } diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index 9c3d6d52..dbe8a9e8 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -2,8 +2,7 @@ use { crate::{ backend, client::{Client, ClientError, ClientId}, - format::XRGB8888, - gfx_api::{GfxFramebuffer, GfxTexture}, + gfx_api::GfxTexture, globals::{Global, GlobalName}, ifs::{ wl_buffer::WlBufferStorage, wl_surface::WlSurface, @@ -19,12 +18,13 @@ use { buffd::{MsgParser, MsgParserError}, clonecell::CloneCell, copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, linkedlist::LinkedList, + transform_ext::TransformExt, }, wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id}, }, ahash::AHashMap, + jay_config::video::Transform, std::{ cell::{Cell, RefCell}, collections::hash_map::Entry, @@ -75,9 +75,10 @@ pub struct WlOutputGlobal { pub destroyed: Cell, pub legacy_scale: Cell, pub preferred_scale: Cell, + pub transform: Cell, } -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Hash)] pub struct OutputId { pub manufacturer: String, pub model: String, @@ -102,16 +103,24 @@ impl WlOutputGlobal { width_mm: i32, height_mm: i32, ) -> Self { + let output_id = Rc::new(OutputId { + manufacturer: manufacturer.to_string(), + model: product.to_string(), + serial_number: serial_number.to_string(), + }); + let transform = state + .output_transforms + .borrow() + .get(&output_id) + .copied() + .unwrap_or(Transform::None); + let (width, height) = transform.maybe_swap((mode.width, mode.height)); Self { name, state: state.clone(), connector: connector.clone(), - pos: Cell::new(Rect::new_sized(x1, 0, mode.width, mode.height).unwrap()), - output_id: Rc::new(OutputId { - manufacturer: manufacturer.to_string(), - model: product.to_string(), - serial_number: serial_number.to_string(), - }), + pos: Cell::new(Rect::new_sized(x1, 0, width, height).unwrap()), + output_id, mode: Cell::new(*mode), node: Default::default(), width_mm, @@ -122,6 +131,7 @@ impl WlOutputGlobal { destroyed: Cell::new(false), legacy_scale: Cell::new(1), preferred_scale: Cell::new(crate::scale::Scale::from_int(1)), + transform: Cell::new(transform), } } @@ -202,13 +212,8 @@ impl WlOutputGlobal { Ok(()) } - pub fn have_shm_screencopies(&self) -> bool { - self.pending_captures.iter().any(|c| c.is_shm.get()) - } - pub fn perform_screencopies( &self, - fb: Option<&dyn GfxFramebuffer>, tex: &Rc, render_hardware_cursors: bool, x_off: i32, @@ -234,55 +239,21 @@ impl WlOutputGlobal { capture.send_failed(); continue; } - let rect = capture.rect; if let Some(WlBufferStorage::Shm { mem, stride }) = wl_buffer.storage.borrow_mut().deref() { - let acc = mem.access(|mem| { - tex.clone().read_pixels( - capture.rect.x1(), - capture.rect.y1(), - capture.rect.width(), - capture.rect.height(), - *stride, - wl_buffer.format, - mem, - ) - }); - let mut res = match acc { - Ok(res) => res, - Err(e) => { - capture.client.error(e); - continue; - } - }; - if res.is_err() { - if let Some(fb) = fb { - let acc = mem.access(|mem| { - fb.copy_to_shm( - rect.x1(), - rect.y1(), - rect.width(), - rect.height(), - XRGB8888, - mem, - ) - }); - res = match acc { - Ok(res) => res, - Err(e) => { - capture.client.error(e); - continue; - } - }; - } - } - if let Err(e) = res { - log::warn!("Could not read texture to memory: {}", ErrorFmt(e)); - capture.send_failed(); - continue; - } - // capture.send_flags(FLAGS_Y_INVERT); + self.state.perform_shm_screencopy( + tex, + self.pos.get(), + x_off, + y_off, + size, + &capture, + mem, + *stride, + wl_buffer.format, + Transform::None, + ); } else { let fb = match wl_buffer.famebuffer.get() { Some(fb) => fb, @@ -295,12 +266,12 @@ impl WlOutputGlobal { self.state.perform_screencopy( tex, &fb, - self.preferred_scale.get(), self.pos.get(), render_hardware_cursors, x_off - capture.rect.x1(), y_off - capture.rect.y1(), size, + Transform::None, ); } if capture.with_damage.get() { @@ -312,6 +283,11 @@ impl WlOutputGlobal { capture.output_link.take(); } } + + pub fn pixel_size(&self) -> (i32, i32) { + let mode = self.mode.get(); + self.transform.get().maybe_swap((mode.width, mode.height)) + } } global_base!(WlOutputGlobal, WlOutput, WlOutputError); @@ -357,7 +333,7 @@ impl WlOutput { subpixel: SP_UNKNOWN, make: &self.global.output_id.manufacturer, model: &self.global.output_id.model, - transform: TF_NORMAL, + transform: self.global.transform.get().to_wl(), }; self.client.event(event); } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 0731aa01..eded34f3 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -57,6 +57,7 @@ use { linkedlist::LinkedNode, numcell::NumCell, rc_eq::rc_eq, + transform_ext::TransformExt, }, wire::{ wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId, @@ -264,11 +265,13 @@ impl WlSeatGlobal { let (x, y) = self.get_position(); for output in self.state.root.outputs.lock().values() { if let Some(hc) = output.hardware_cursor.get() { + let transform = output.global.transform.get(); let render = render | output.hardware_cursor_needs_render.take(); let scale = output.global.preferred_scale.get(); let extents = cursor.extents_at_scale(scale); + let (hc_width, hc_height) = hc.size(); if render { - let (max_width, max_height) = hc.max_size(); + let (max_width, max_height) = transform.maybe_swap((hc_width, hc_height)); if extents.width() > max_width || extents.height() > max_height { hc.set_enabled(false); hc.commit(); @@ -285,17 +288,25 @@ impl WlSeatGlobal { x_rel = ((x - Fixed::from_int(opos.x1())).to_f64() * scalef).round() as i32; y_rel = ((y - Fixed::from_int(opos.y1())).to_f64() * scalef).round() as i32; } - let mode = output.global.mode.get(); - if extents - .intersects(&Rect::new_sized(-x_rel, -y_rel, mode.width, mode.height).unwrap()) - { + let (width, height) = output.global.pixel_size(); + if extents.intersects(&Rect::new_sized(-x_rel, -y_rel, width, height).unwrap()) { if render { let buffer = hc.get_buffer(); - buffer.render_hardware_cursor(cursor.deref(), &self.state, scale); + buffer.render_hardware_cursor( + cursor.deref(), + &self.state, + scale, + transform, + ); hc.swap_buffer(); } hc.set_enabled(true); - hc.set_position(x_rel + extents.x1(), y_rel + extents.y1()); + let mode = output.global.mode.get(); + let (x_rel, y_rel) = + transform.apply_point(mode.width, mode.height, (x_rel, y_rel)); + let (hot_x, hot_y) = + transform.apply_point(hc_width, hc_height, (-extents.x1(), -extents.y1())); + hc.set_position(x_rel - hot_x, y_rel - hot_y); } else { if render { output.hardware_cursor_needs_render.set(true); diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 710d2fa2..b8f34d71 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -16,14 +16,10 @@ use { client::{Client, ClientError, RequestParser}, drm_feedback::DrmFeedback, fixed::Fixed, - gfx_api::{BufferPoint, BufferPoints}, + gfx_api::SampleRect, ifs::{ wl_buffer::WlBuffer, wl_callback::WlCallback, - wl_output::{ - TF_180, TF_270, TF_90, TF_FLIPPED, TF_FLIPPED_180, TF_FLIPPED_270, TF_FLIPPED_90, - TF_NORMAL, - }, wl_seat::{ wl_pointer::PendingScroll, zwp_pointer_constraints_v1::SeatConstraint, Dnd, NodeSeatState, SeatId, WlSeatGlobal, @@ -54,6 +50,7 @@ use { linkedlist::LinkedList, numcell::NumCell, smallmap::SmallMap, + transform_ext::TransformExt, }, wire::{ wl_surface::*, WlOutputId, WlSurfaceId, ZwpIdleInhibitorV1Id, @@ -63,6 +60,7 @@ use { xwayland::XWaylandEvent, }, ahash::AHashMap, + jay_config::video::Transform, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, @@ -83,110 +81,7 @@ const INVALID_SIZE: u32 = 2; const OFFSET_SINCE: u32 = 5; const BUFFER_SCALE_SINCE: u32 = 6; - -#[derive(Copy, Clone, Debug, PartialEq)] -enum Transform { - Normal, - Rotate90, - Rotate180, - Rotate270, - Flipped, - Flipped90, - Flipped180, - Flipped270, -} - -impl Transform { - fn swaps_dimensions(self) -> bool { - match self { - Transform::Normal => false, - Transform::Rotate90 => true, - Transform::Rotate180 => false, - Transform::Rotate270 => true, - Transform::Flipped => false, - Transform::Flipped90 => true, - Transform::Flipped180 => false, - Transform::Flipped270 => true, - } - } -} - -impl Transform { - fn apply_inv_sized(self, x1: f32, y1: f32, width: f32, height: f32) -> BufferPoints { - let x2 = x1 + width; - let y2 = y1 + height; - self.apply_inv(x1, y1, x2, y2) - } - - fn apply_inv(self, x1: f32, y1: f32, x2: f32, y2: f32) -> BufferPoints { - macro_rules! bp { - ( - $tl_x:expr, $tl_y:expr, - $tr_x:expr, $tr_y:expr, - $br_x:expr, $br_y:expr, - $bl_x:expr, $bl_y:expr, - ) => { - BufferPoints { - top_left: BufferPoint { x: $tl_x, y: $tl_y }, - top_right: BufferPoint { x: $tr_x, y: $tr_y }, - bottom_right: BufferPoint { x: $br_x, y: $br_y }, - bottom_left: BufferPoint { x: $bl_x, y: $bl_y }, - } - }; - } - use Transform::*; - match self { - Normal => bp! { - x1, y1, - x2, y1, - x2, y2, - x1, y2, - }, - Rotate90 => bp! { - y1, x2, - y1, x1, - y2, x1, - y2, x2, - }, - Rotate180 => bp! { - x2, y2, - x1, y2, - x1, y1, - x2, y1, - }, - Rotate270 => bp! { - y2, x1, - y2, x2, - y1, x2, - y1, x1, - }, - Flipped => bp! { - x2, y1, - x1, y1, - x1, y2, - x2, y2, - }, - Flipped90 => bp! { - y1, x1, - y1, x2, - y2, x2, - y2, x1, - }, - Flipped180 => bp! { - x1, y2, - x2, y2, - x2, y1, - x1, y1, - }, - Flipped270 => bp! { - y2, x2, - y2, x1, - y1, x1, - y1, x2, - }, - } - } -} +const TRANSFORM_SINCE: u32 = 6; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum SurfaceRole { @@ -223,6 +118,14 @@ impl NodeVisitorBase for SurfaceSendPreferredScaleVisitor { } } +pub struct SurfaceSendPreferredTransformVisitor; +impl NodeVisitorBase for SurfaceSendPreferredTransformVisitor { + fn visit_surface(&mut self, node: &Rc) { + node.send_preferred_buffer_transform(); + node.node_visit_children(self); + } +} + pub struct WlSurface { pub id: WlSurfaceId, pub node_id: SurfaceNodeId, @@ -233,7 +136,7 @@ pub struct WlSurface { input_region: Cell>>, opaque_region: Cell>>, buffer_points: RefCell, - pub buffer_points_norm: RefCell, + pub buffer_points_norm: RefCell, buffer_transform: Cell, buffer_scale: Cell, src_rect: Cell>, @@ -273,6 +176,14 @@ impl Debug for WlSurface { } } +#[derive(Default)] +struct BufferPoints { + x1: f32, + x2: f32, + y1: f32, + y2: f32, +} + #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum CommitContext { RootCommit, @@ -387,7 +298,7 @@ impl WlSurface { opaque_region: Default::default(), buffer_points: Default::default(), buffer_points_norm: Default::default(), - buffer_transform: Cell::new(Transform::Normal), + buffer_transform: Cell::new(Transform::None), buffer_scale: Cell::new(1), src_rect: Cell::new(None), dst_size: Cell::new(None), @@ -448,6 +359,9 @@ impl WlSurface { if old.global.preferred_scale.get() != output.global.preferred_scale.get() { self.on_scale_change(); } + if old.global.transform.get() != output.global.transform.get() { + self.send_preferred_buffer_transform(); + } let children = self.children.borrow_mut(); if let Some(children) = &*children { for ss in children.subsurfaces.values() { @@ -540,6 +454,15 @@ impl WlSurface { } } + pub fn send_preferred_buffer_transform(&self) { + if self.version >= TRANSFORM_SINCE { + self.client.event(PreferredBufferTransform { + self_id: self.id, + transform: self.output.get().global.transform.get().to_wl() as _, + }); + } + } + fn set_toplevel(&self, tl: Option>) { let ch = self.children.borrow(); if let Some(ch) = &*ch { @@ -823,10 +746,12 @@ impl WlSurface { width *= scale; height *= scale; } - *buffer_points = self - .buffer_transform - .get() - .apply_inv_sized(x1, y1, width, height); + *buffer_points = BufferPoints { + x1, + y1, + x2: x1 + width, + y2: y1 + height, + }; } let size = match self.dst_size.get() { Some(ds) => ds, @@ -838,10 +763,8 @@ impl WlSurface { } if let Some(buffer) = self.buffer.get() { if new_size.is_none() { - let (mut width, mut height) = buffer.rect.size(); - if self.buffer_transform.get().swaps_dimensions() { - mem::swap(&mut width, &mut height); - } + let (mut width, mut height) = + self.buffer_transform.get().maybe_swap(buffer.rect.size()); let scale = self.buffer_scale.get(); if scale != 1 { width = (width + scale - 1) / scale; @@ -850,19 +773,29 @@ impl WlSurface { new_size = Some((width, height)); } if transform_changed || Some(buffer.rect) != old_raw_size { - if self.src_rect.get().is_none() { - *buffer_points = self - .buffer_transform - .get() - .apply_inv_sized(0.0, 0.0, 1.0, 1.0); - *buffer_points_norm = *buffer_points; + let (x1, y1, x2, y2) = if self.src_rect.get().is_none() { + (0.0, 0.0, 1.0, 1.0) } else { - *buffer_points_norm = buffer_points - .norm(buffer.rect.width() as f32, buffer.rect.height() as f32); - if !buffer_points_norm.is_leq_1() { + let (width, height) = + self.buffer_transform.get().maybe_swap(buffer.rect.size()); + let width = width as f32; + let height = height as f32; + let x1 = buffer_points.x1 / width; + let x2 = buffer_points.x2 / width; + let y1 = buffer_points.y1 / height; + let y2 = buffer_points.y2 / height; + if x1 > 1.0 || x2 > 1.0 || y1 > 1.0 || y2 > 1.0 { return Err(WlSurfaceError::ViewportOutsideBuffer); } - } + (x1, y1, x2, y2) + }; + *buffer_points_norm = SampleRect { + x1, + y1, + x2, + y2, + buffer_transform: self.buffer_transform.get(), + }; } } let (width, height) = new_size.unwrap_or_default(); @@ -932,17 +865,8 @@ impl WlSurface { fn set_buffer_transform(&self, parser: MsgParser<'_, '_>) -> Result<(), WlSurfaceError> { let req: SetBufferTransform = self.parse(parser)?; - use Transform::*; - let tf = match req.transform { - TF_NORMAL => Normal, - TF_90 => Rotate90, - TF_180 => Rotate180, - TF_270 => Rotate270, - TF_FLIPPED => Flipped, - TF_FLIPPED_90 => Flipped90, - TF_FLIPPED_180 => Flipped180, - TF_FLIPPED_270 => Flipped270, - _ => return Err(WlSurfaceError::UnknownBufferTransform(req.transform)), + let Some(tf) = Transform::from_wl(req.transform) else { + return Err(WlSurfaceError::UnknownBufferTransform(req.transform)); }; self.pending.transform.set(Some(tf)); Ok(()) diff --git a/src/ifs/zwlr_screencopy_frame_v1.rs b/src/ifs/zwlr_screencopy_frame_v1.rs index bfca58d5..92a639fd 100644 --- a/src/ifs/zwlr_screencopy_frame_v1.rs +++ b/src/ifs/zwlr_screencopy_frame_v1.rs @@ -33,7 +33,6 @@ pub struct ZwlrScreencopyFrameV1 { pub with_damage: Cell, pub output_link: Cell>>>, pub buffer: Cell>>, - pub is_shm: Cell, pub version: u32, } @@ -121,14 +120,6 @@ impl ZwlrScreencopyFrameV1 { return Err(ZwlrScreencopyFrameV1Error::InvalidBufferStride); } } - let is_shm = match &*buffer.storage.borrow() { - None => false, - Some(s) => match s { - WlBufferStorage::Shm { .. } => true, - WlBufferStorage::Dmabuf(_) => false, - }, - }; - self.is_shm.set(is_shm); self.buffer.set(Some(buffer)); if !with_damage { self.output.connector.connector.damage(); diff --git a/src/ifs/zwlr_screencopy_manager_v1.rs b/src/ifs/zwlr_screencopy_manager_v1.rs index 28fcfadf..3e653c28 100644 --- a/src/ifs/zwlr_screencopy_manager_v1.rs +++ b/src/ifs/zwlr_screencopy_manager_v1.rs @@ -119,7 +119,6 @@ impl ZwlrScreencopyManagerV1 { with_damage: Cell::new(false), output_link: Cell::new(None), buffer: Cell::new(None), - is_shm: Cell::new(false), version: self.version, }); track!(self.client, frame); diff --git a/src/pipewire/pw_ifs/pw_client_node.rs b/src/pipewire/pw_ifs/pw_client_node.rs index 11f47b18..ac139ac1 100644 --- a/src/pipewire/pw_ifs/pw_client_node.rs +++ b/src/pipewire/pw_ifs/pw_client_node.rs @@ -25,8 +25,9 @@ use { SPA_PARAM_Meta, SpaDataFlags, SpaDataType, SpaDirection, SpaIoType, SpaMediaSubtype, SpaMediaType, SpaMetaType, SpaNodeBuffersFlags, SpaNodeCommand, SpaParamType, SpaVideoFormat, SPA_DATA_FLAG_READABLE, SPA_DIRECTION_INPUT, - SPA_DIRECTION_OUTPUT, SPA_NODE_BUFFERS_FLAG_ALLOC, SPA_PARAM_INFO_READ, - SPA_PORT_FLAG, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS, + SPA_DIRECTION_OUTPUT, SPA_NODE_BUFFERS_FLAG_ALLOC, SPA_PARAM_INFO, + SPA_PARAM_INFO_READ, SPA_PARAM_INFO_SERIAL, SPA_PORT_FLAG, + SPA_PORT_FLAG_CAN_ALLOC_BUFFERS, }, }, utils::{ @@ -120,6 +121,8 @@ pub struct PwClientNodePort { pub buffer_config: Cell>, pub io_buffers: CopyHashMap>>, + + pub serial: Cell, } #[derive(Copy, Clone, Debug, Default)] @@ -255,6 +258,7 @@ impl PwClientNode { buffers: RefCell::new(vec![]), buffer_config: Cell::new(None), io_buffers: Default::default(), + serial: Cell::new(false), }); self.ports.set((direction, port.id), port.clone()); port @@ -291,7 +295,14 @@ impl PwClientNode { }); } - pub fn send_port_update(&self, port: &PwClientNodePort) { + pub fn send_port_update(&self, port: &PwClientNodePort, re_init: bool) { + if re_init { + port.serial.set(!port.serial.get()); + } + let serial = match port.serial.get() { + true => SPA_PARAM_INFO_SERIAL, + false => SPA_PARAM_INFO::none(), + }; self.con.send(self, PwClientNodeMethods::PortUpdate, |f| { f.write_struct(|f| { // direction @@ -437,11 +448,11 @@ impl PwClientNode { f.write_uint(num_params); if sf.is_some() { f.write_id(SPA_PARAM_EnumFormat.0); - f.write_uint(SPA_PARAM_INFO_READ.0); + f.write_uint((SPA_PARAM_INFO_READ | serial).0); } if bc.is_some() { f.write_id(SPA_PARAM_Buffers.0); - f.write_uint(SPA_PARAM_INFO_READ.0); + f.write_uint((SPA_PARAM_INFO_READ | serial).0); } f.write_id(SPA_PARAM_Meta.0); f.write_uint(SPA_PARAM_INFO_READ.0); diff --git a/src/portal/ptl_screencast.rs b/src/portal/ptl_screencast.rs index 60502f52..e1aa2b5c 100644 --- a/src/portal/ptl_screencast.rs +++ b/src/portal/ptl_screencast.rs @@ -155,7 +155,7 @@ impl PwClientNodeOwner for StartingScreencast { impl PwClientNodeOwner for StartedScreencast { fn port_format_changed(&self, port: &Rc) { - self.node.send_port_update(port); + self.node.send_port_update(port, false); } fn use_buffers(&self, port: &Rc) { @@ -300,7 +300,7 @@ impl UsrJayScreencastOwner for StartedScreencast { data_type: SPA_DATA_DmaBuf, }; self.port.buffer_config.set(Some(bc)); - self.node.send_port_update(&self.port); + self.node.send_port_update(&self.port, true); self.node.send_active(true); *self.buffers.borrow_mut() = buffers; } diff --git a/src/renderer.rs b/src/renderer.rs index 965c7dc5..dcb2fd0b 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,6 +1,6 @@ use { crate::{ - gfx_api::{BufferPoints, GfxApiOpt}, + gfx_api::{GfxApiOpt, SampleRect}, ifs::{ wl_buffer::WlBuffer, wl_callback::WlCallback, @@ -55,7 +55,7 @@ pub struct Renderer<'a> { pub state: &'a State, pub result: Option<&'a mut RenderResult>, pub logical_extents: Rect, - pub physical_extents: Rect, + pub pixel_extents: Rect, } impl Renderer<'_> { @@ -63,8 +63,8 @@ impl Renderer<'_> { self.base.scale } - pub fn physical_extents(&self) -> Rect { - self.physical_extents + pub fn pixel_extents(&self) -> Rect { + self.pixel_extents } pub fn logical_extents(&self) -> Rect { @@ -350,7 +350,7 @@ impl Renderer<'_> { buffer: &WlBuffer, x: i32, y: i32, - tpoints: BufferPoints, + tpoints: SampleRect, tsize: (i32, i32), bounds: Option<&Rect>, ) { diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index e3f294a2..9d935d3b 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -1,12 +1,12 @@ use { crate::{ - gfx_api::{ - AbsoluteRect, BufferPoint, BufferPoints, CopyTexture, FillRect, GfxApiOpt, GfxTexture, - }, + gfx_api::{CopyTexture, FillRect, FramebufferRect, GfxApiOpt, GfxTexture, SampleRect}, rect::Rect, scale::Scale, theme::Color, + utils::transform_ext::TransformExt, }, + jay_config::video::Transform, std::rc::Rc, }; @@ -15,6 +15,9 @@ pub struct RendererBase<'a> { pub scaled: bool, pub scale: Scale, pub scalef: f64, + pub transform: Transform, + pub fb_width: f32, + pub fb_height: f32, } impl RendererBase<'_> { @@ -72,12 +75,15 @@ impl RendererBase<'_> { for bx in boxes { let bx = self.scale_rect(*bx); self.ops.push(GfxApiOpt::FillRect(FillRect { - rect: AbsoluteRect { - x1: (bx.x1() + dx) as f32, - y1: (bx.y1() + dy) as f32, - x2: (bx.x2() + dx) as f32, - y2: (bx.y2() + dy) as f32, - }, + rect: FramebufferRect::new( + (bx.x1() + dx) as f32, + (bx.y1() + dy) as f32, + (bx.x2() + dx) as f32, + (bx.y2() + dy) as f32, + self.transform, + self.fb_width, + self.fb_height, + ), color: *color, })); } @@ -101,12 +107,15 @@ impl RendererBase<'_> { for bx in boxes { let (x1, y1, x2, y2) = self.scale_rect_f(*bx); self.ops.push(GfxApiOpt::FillRect(FillRect { - rect: AbsoluteRect { - x1: x1 + dx, - y1: y1 + dy, - x2: x2 + dx, - y2: y2 + dy, - }, + rect: FramebufferRect::new( + x1 + dx, + y1 + dy, + x2 + dx, + y2 + dy, + self.transform, + self.fb_width, + self.fb_height, + ), color: *color, })); } @@ -117,22 +126,17 @@ impl RendererBase<'_> { texture: &Rc, x: i32, y: i32, - tpoints: Option, + tpoints: Option, tsize: Option<(i32, i32)>, tscale: Scale, bounds: Option<&Rect>, ) { - let mut texcoord = tpoints.unwrap_or(BufferPoints { - top_left: BufferPoint { x: 0.0, y: 0.0 }, - top_right: BufferPoint { x: 1.0, y: 0.0 }, - bottom_left: BufferPoint { x: 0.0, y: 1.0 }, - bottom_right: BufferPoint { x: 1.0, y: 1.0 }, - }); + let mut texcoord = tpoints.unwrap_or_else(SampleRect::identity); let (twidth, theight) = if let Some(size) = tsize { size } else { - let (mut w, mut h) = texture.size(); + let (mut w, mut h) = texcoord.buffer_transform.maybe_swap(texture.size()); if tscale != self.scale { let tscale = tscale.to_f64(); w = (w as f64 * self.scalef / tscale).round() as _; @@ -145,86 +149,80 @@ impl RendererBase<'_> { let mut target_y = [y, y + theight]; if let Some(bounds) = bounds { - #[cold] - fn cold() {} - - let bounds_x = [bounds.x1(), bounds.x2()]; - let bounds_y = [bounds.y1(), bounds.y2()]; - - macro_rules! clamp { - ($desired:ident, $bounds:ident, $test_idx:expr, $test_cmp:ident, $test_cmp_eq:ident, $([$modify:ident, $keep:ident],)*) => {{ - let desired_test = $desired[$test_idx]; - let desired_other = $desired[1 - $test_idx]; - let bound = $bounds[$test_idx]; - if desired_test.$test_cmp(&bound) { - cold(); - if desired_other.$test_cmp_eq(&bound) { - return; - } - let max = (desired_other - bound) as f32; - let desired = ($desired[1] - $desired[0]) as f32; - let factor = max.abs() / desired; - $( - let dx = (texcoord.$modify.x - texcoord.$keep.x) * factor; - texcoord.$modify.x = texcoord.$keep.x + dx; - let dy = (texcoord.$modify.y - texcoord.$keep.y) * factor; - texcoord.$modify.y = texcoord.$keep.y + dy; - )* - $desired[$test_idx] = bound; - } - }}; + if bound_target(&mut target_x, &mut target_y, &mut texcoord, bounds) { + return; } - - clamp!( - target_x, - bounds_x, - 0, - lt, - le, - [top_left, top_right], - [bottom_left, bottom_right], - ); - - clamp!( - target_x, - bounds_x, - 1, - gt, - ge, - [top_right, top_left], - [bottom_right, bottom_left], - ); - - clamp!( - target_y, - bounds_y, - 0, - lt, - le, - [top_left, bottom_left], - [top_right, bottom_right], - ); - - clamp!( - target_y, - bounds_y, - 1, - gt, - ge, - [bottom_left, top_left], - [bottom_right, top_right], - ); } self.ops.push(GfxApiOpt::CopyTexture(CopyTexture { tex: texture.clone(), source: texcoord, - target: AbsoluteRect { - x1: target_x[0] as f32, - y1: target_y[0] as f32, - x2: target_x[1] as f32, - y2: target_y[1] as f32, - }, + target: FramebufferRect::new( + target_x[0] as f32, + target_y[0] as f32, + target_x[1] as f32, + target_y[1] as f32, + self.transform, + self.fb_width, + self.fb_height, + ), })); } } + +#[inline] +fn bound_target( + target_x: &mut [i32; 2], + target_y: &mut [i32; 2], + texcoord: &mut SampleRect, + bounds: &Rect, +) -> bool { + let bounds_x = [bounds.x1(), bounds.x2()]; + let bounds_y = [bounds.y1(), bounds.y2()]; + + if target_x[0] >= bounds_x[0] + && target_x[1] <= bounds_x[1] + && target_y[0] >= bounds_y[0] + && target_y[1] <= bounds_y[1] + { + return false; + } + + #[cold] + fn cold() {} + cold(); + + let SampleRect { + x1: ref mut t_x1, + x2: ref mut t_x2, + y1: ref mut t_y1, + y2: ref mut t_y2, + .. + } = texcoord; + + macro_rules! clamp { + ($desired:ident, $bounds:ident, $test_idx:expr, $test_cmp:ident, $test_cmp_eq:ident, $modify:ident, $keep:ident) => {{ + let desired_test = $desired[$test_idx]; + let desired_other = $desired[1 - $test_idx]; + let bound = $bounds[$test_idx]; + if desired_test.$test_cmp(&bound) { + cold(); + if desired_other.$test_cmp_eq(&bound) { + return true; + } + let max = (desired_other - bound) as f32; + let desired = ($desired[1] - $desired[0]) as f32; + let factor = max.abs() / desired; + *$modify = *$keep + (*$modify - *$keep) * factor; + $desired[$test_idx] = bound; + } + }}; + } + + clamp!(target_x, bounds_x, 0, lt, le, t_x1, t_x2); + clamp!(target_x, bounds_x, 1, gt, ge, t_x2, t_x1); + clamp!(target_y, bounds_y, 0, lt, le, t_y1, t_y2); + clamp!(target_y, bounds_y, 1, gt, ge, t_y2, t_y1); + + false +} diff --git a/src/screenshoter.rs b/src/screenshoter.rs index e7c83298..ec4b3f66 100644 --- a/src/screenshoter.rs +++ b/src/screenshoter.rs @@ -10,6 +10,7 @@ use { INVALID_MODIFIER, LINEAR_MODIFIER, }, }, + jay_config::video::Transform, std::{ops::Deref, rc::Rc}, thiserror::Error, uapi::OwnedFd, @@ -76,6 +77,7 @@ pub fn take_screenshot(state: &State) -> Result Scale::from_int(1), true, false, + Transform::None, ); let drm = gbm.drm.dup_render()?.fd().clone(); Ok(Screenshot { drm, bo }) diff --git a/src/state.rs b/src/state.rs index 7a4895ec..aa535e21 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,13 +9,15 @@ use { backends::dummy::DummyBackend, cli::RunArgs, client::{Client, ClientId, Clients, SerialRange, NUM_CACHED_SERIAL_RANGES}, + clientmem::ClientMemOffset, config::ConfigProxy, cursor::{Cursor, ServerCursors}, dbus::Dbus, drm_feedback::{DrmFeedback, DrmFeedbackIds}, fixed::Fixed, forker::ForkerProxy, - gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, + format::Format, + gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture, SampleRect}, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, WaylandGlobal}, ifs::{ @@ -25,11 +27,13 @@ use { jay_seat_events::JaySeatEvents, jay_workspace_watcher::JayWorkspaceWatcher, wl_drm::WlDrmGlobal, + wl_output::OutputId, wl_seat::{SeatIds, WlSeatGlobal}, wl_surface::{ zwp_idle_inhibitor_v1::{IdleInhibitorId, IdleInhibitorIds, ZwpIdleInhibitorV1}, NoneSurfaceExt, WlSurface, }, + zwlr_screencopy_frame_v1::ZwlrScreencopyFrameV1, zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1, zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1Global, }, @@ -37,7 +41,7 @@ use { leaks::Tracker, logger::Logger, rect::Rect, - renderer::{renderer_base::RendererBase, RenderResult, Renderer}, + renderer::{RenderResult, Renderer}, scale::Scale, theme::{Color, Theme}, tree::{ @@ -62,7 +66,10 @@ use { }, ahash::AHashMap, bstr::ByteSlice, - jay_config::{video::GfxApi, PciId}, + jay_config::{ + video::{GfxApi, Transform}, + PciId, + }, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, @@ -146,6 +153,7 @@ pub struct State { pub dma_buf_ids: DmaBufIds, pub drm_feedback_ids: DrmFeedbackIds, pub direct_scanout_enabled: Cell, + pub output_transforms: RefCell, Transform>>, } // impl Drop for State { @@ -751,7 +759,7 @@ impl State { output.global.preferred_scale.get(), render_hw_cursor, ); - output.perform_screencopies(Some(&**fb), tex, !render_hw_cursor, 0, 0, None); + output.perform_screencopies(tex, !render_hw_cursor, 0, 0, None); rr.dispatch_frame_requests(); } @@ -759,30 +767,35 @@ impl State { &self, src: &Rc, target: &Rc, - scale: Scale, position: Rect, render_hardware_cursors: bool, x_off: i32, y_off: i32, size: Option<(i32, i32)>, + transform: Transform, ) { let mut ops = target.take_render_ops(); - let (width, height) = target.size(); let mut renderer = Renderer { - base: RendererBase { - ops: &mut ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), - }, + base: target.renderer_base(&mut ops, Scale::from_int(1), Transform::None), state: self, result: None, logical_extents: position.at_point(0, 0), - physical_extents: Rect::new_sized(0, 0, width, height).unwrap(), + pixel_extents: { + let (width, height) = target.logical_size(Transform::None); + Rect::new_sized(0, 0, width, height).unwrap() + }, }; - renderer - .base - .render_texture(src, x_off, y_off, None, size, scale, None); + let mut sample_rect = SampleRect::identity(); + sample_rect.buffer_transform = transform; + renderer.base.render_texture( + src, + x_off, + y_off, + Some(sample_rect), + size, + Scale::from_int(1), + None, + ); if render_hardware_cursors { for seat in self.globals.lock_seats().values() { if let Some(cursor) = seat.get_cursor() { @@ -797,4 +810,103 @@ impl State { } target.render(ops, Some(&Color::SOLID_BLACK)); } + + fn have_hardware_cursor(&self) -> bool { + for seat in self.globals.lock_seats().values() { + if seat.get_cursor().is_some() { + if seat.hardware_cursor() { + return true; + } + } + } + false + } + + pub fn perform_shm_screencopy( + &self, + src: &Rc, + position: Rect, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + capture: &ZwlrScreencopyFrameV1, + mem: &ClientMemOffset, + stride: i32, + format: &'static Format, + transform: Transform, + ) { + let (src_width, src_height) = src.size(); + let mut needs_copy = capture.rect.x1() < x_off + || capture.rect.x2() > x_off + src_width + || capture.rect.y1() < y_off + || capture.rect.y2() > y_off + src_height + || self.have_hardware_cursor() + || transform != Transform::None; + if let Some((target_width, target_height)) = size { + if (target_width, target_height) != (src_width, src_height) { + needs_copy = true; + } + } + let acc = if needs_copy { + let Some(ctx) = self.render_ctx.get() else { + log::warn!("Cannot perform shm screencopy because there is no render context"); + return; + }; + let fb = + match ctx.create_fb(capture.rect.width(), capture.rect.height(), stride, format) { + Ok(f) => f, + Err(e) => { + log::warn!( + "Could not create temporary fb for screencopy: {}", + ErrorFmt(e) + ); + return; + } + }; + self.perform_screencopy( + src, + &fb, + position, + true, + x_off - capture.rect.x1(), + y_off - capture.rect.y1(), + size, + transform, + ); + mem.access(|mem| { + fb.copy_to_shm( + 0, + 0, + capture.rect.width(), + capture.rect.height(), + stride, + format, + mem, + ) + }) + } else { + mem.access(|mem| { + src.clone().read_pixels( + capture.rect.x1() - x_off, + capture.rect.y1() - y_off, + capture.rect.width(), + capture.rect.height(), + stride, + format, + mem, + ) + }) + }; + let res = match acc { + Ok(res) => res, + Err(e) => { + capture.client.error(e); + return; + } + }; + if let Err(e) = res { + log::warn!("Could not read texture to memory: {}", ErrorFmt(e)); + capture.send_failed(); + } + } } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 447b5eaa..d75685e9 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -269,8 +269,7 @@ impl ConnectorHandler { for seat in seats.values() { if seat.get_output().id == on.id { let tpos = target.global.pos.get(); - let tmode = target.global.mode.get(); - seat.set_position(tpos.x1() + tmode.width / 2, tpos.y1() + tmode.height / 2); + seat.set_position((tpos.x1() + tpos.x2()) / 2, (tpos.y1() + tpos.y2()) / 2); } } if let Some(dev) = &self.data.drm_dev { diff --git a/src/tree/output.rs b/src/tree/output.rs index 3c7e4f99..b5ea0216 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -4,7 +4,7 @@ use { client::ClientId, cursor::KnownCursor, fixed::Fixed, - gfx_api::{GfxFramebuffer, GfxTexture}, + gfx_api::GfxTexture, ifs::{ jay_output::JayOutput, jay_screencast::JayScreencast, @@ -16,6 +16,7 @@ use { wl_surface::{ ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, SurfaceSendPreferredScaleVisitor, + SurfaceSendPreferredTransformVisitor, }, zwlr_layer_shell_v1::{BACKGROUND, BOTTOM, OVERLAY, TOP}, }, @@ -34,6 +35,7 @@ use { wire::{JayOutputId, JayScreencastId}, }, ahash::AHashMap, + jay_config::video::Transform, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, @@ -80,7 +82,6 @@ pub async fn output_render_data(state: Rc) { impl OutputNode { pub fn perform_screencopies( &self, - fb: Option<&dyn GfxFramebuffer>, tex: &Rc, render_hardware_cursor: bool, x_off: i32, @@ -93,7 +94,7 @@ impl OutputNode { } } self.global - .perform_screencopies(fb, tex, render_hardware_cursor, x_off, y_off, size); + .perform_screencopies(tex, render_hardware_cursor, x_off, y_off, size); for sc in self.screencasts.lock().values() { sc.copy_texture(self, tex, render_hardware_cursor, x_off, y_off, size); } @@ -387,15 +388,30 @@ impl OutputNode { } pub fn update_mode(self: &Rc, mode: Mode) { + self.update_mode_and_transform(mode, self.global.transform.get()); + } + + pub fn update_transform(self: &Rc, transform: Transform) { + self.update_mode_and_transform(self.global.mode.get(), transform); + } + + pub fn update_mode_and_transform(self: &Rc, mode: Mode, transform: Transform) { let old_mode = self.global.mode.get(); - if old_mode == mode { + let old_transform = self.global.transform.get(); + if (old_mode, old_transform) == (mode, transform) { return; } + let (old_width, old_height) = self.global.pixel_size(); self.global.mode.set(mode); - let rect = self.calculate_extents(); - self.change_extents_(&rect); - - if (old_mode.width, old_mode.height) != (mode.width, mode.height) { + self.state + .output_transforms + .borrow_mut() + .insert(self.global.output_id.clone(), transform); + self.global.transform.set(transform); + let (new_width, new_height) = self.global.pixel_size(); + self.change_extents_(&self.calculate_extents()); + + if (old_width, old_height) != (new_width, new_height) { let mut to_destroy = vec![]; if let Some(ctx) = self.state.render_ctx.get() { for sc in self.screencasts.lock().values() { @@ -412,12 +428,15 @@ impl OutputNode { sc.do_destroy(); } } + + if transform != old_transform { + self.state.refresh_hardware_cursors(); + self.node_visit_children(&mut SurfaceSendPreferredTransformVisitor); + } } fn calculate_extents(&self) -> Rect { - let mode = self.global.mode.get(); - let mut width = mode.width; - let mut height = mode.height; + let (mut width, mut height) = self.global.pixel_size(); let scale = self.global.preferred_scale.get(); if scale != 1 { let scale = scale.to_f64(); diff --git a/src/utils.rs b/src/utils.rs index 13a84df6..d30fd686 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -36,6 +36,7 @@ pub mod syncqueue; pub mod threshold_counter; pub mod timer; pub mod toplevel_identifier; +pub mod transform_ext; pub mod tri; pub mod trim; pub mod unlink_on_drop; diff --git a/src/utils/transform_ext.rs b/src/utils/transform_ext.rs new file mode 100644 index 00000000..63f5a975 --- /dev/null +++ b/src/utils/transform_ext.rs @@ -0,0 +1,71 @@ +use { + crate::ifs::wl_output::{ + TF_180, TF_270, TF_90, TF_FLIPPED, TF_FLIPPED_180, TF_FLIPPED_270, TF_FLIPPED_90, TF_NORMAL, + }, + jay_config::video::{ + Transform, + Transform::{ + Flip, FlipRotate180, FlipRotate270, FlipRotate90, None, Rotate180, Rotate270, Rotate90, + }, + }, +}; + +pub trait TransformExt: Sized { + fn maybe_swap(self, args: (T, T)) -> (T, T); + + fn to_wl(self) -> i32; + + fn from_wl(wl: i32) -> Option; + + fn apply_point(self, width: i32, height: i32, point: (i32, i32)) -> (i32, i32); +} + +impl TransformExt for Transform { + fn maybe_swap(self, (left, right): (T, T)) -> (T, T) { + match self { + None | Rotate180 | Flip | FlipRotate180 => (left, right), + Rotate90 | Rotate270 | FlipRotate90 | FlipRotate270 => (right, left), + } + } + + fn to_wl(self) -> i32 { + match self { + None => TF_NORMAL, + Rotate90 => TF_90, + Rotate180 => TF_180, + Rotate270 => TF_270, + Flip => TF_FLIPPED, + FlipRotate90 => TF_FLIPPED_90, + FlipRotate180 => TF_FLIPPED_180, + FlipRotate270 => TF_FLIPPED_270, + } + } + + fn from_wl(wl: i32) -> Option { + let tf = match wl { + TF_NORMAL => None, + TF_90 => Rotate90, + TF_180 => Rotate180, + TF_270 => Rotate270, + TF_FLIPPED => Flip, + TF_FLIPPED_90 => FlipRotate90, + TF_FLIPPED_180 => FlipRotate180, + TF_FLIPPED_270 => FlipRotate270, + _ => return Option::None, + }; + Some(tf) + } + + fn apply_point(self, width: i32, height: i32, (x, y): (i32, i32)) -> (i32, i32) { + match self { + None => (x, y), + Rotate90 => (y, height - x), + Rotate180 => (width - x, height - y), + Rotate270 => (width - y, x), + Flip => (width - x, y), + FlipRotate90 => (y, x), + FlipRotate180 => (x, height - y), + FlipRotate270 => (width - y, height - x), + } + } +}