From b7d2964e1938737fcd98688bde50b45d84bdc5f0 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 27 Feb 2024 23:32:40 +0100 Subject: [PATCH] all: implement output transforms --- jay-config/src/_private/client.rs | 9 ++- jay-config/src/_private/ipc.rs | 6 +- jay-config/src/video.rs | 9 +++ src/backend.rs | 2 +- src/backends/metal/video.rs | 22 +++++--- src/compositor.rs | 1 + src/config/handler.rs | 19 ++++++- src/cursor.rs | 2 +- src/gfx_api.rs | 74 ++++++++++++++++++++----- src/gfx_apis/gl/renderer/framebuffer.rs | 2 +- src/gfx_apis/vulkan/image.rs | 2 +- src/ifs/jay_screencast.rs | 12 ++-- src/ifs/wl_output.rs | 35 +++++++++--- src/ifs/wl_seat.rs | 25 ++++++--- src/ifs/wl_surface.rs | 21 +++++++ src/renderer.rs | 6 +- src/renderer/renderer_base.rs | 5 ++ src/screenshoter.rs | 2 + src/state.rs | 37 ++++++++++--- src/tree/output.rs | 34 +++++++++--- 20 files changed, 254 insertions(+), 71 deletions(-) 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 9ba2a96b..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. 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 0a799a2f..75b2ff3f 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -20,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, @@ -37,7 +38,7 @@ use { ahash::{AHashMap, AHashSet}, bstr::{BString, ByteSlice}, indexmap::{indexset, IndexSet}, - jay_config::video::{GfxApi, Transform}, + jay_config::video::GfxApi, std::{ cell::{Cell, RefCell}, ffi::CString, @@ -284,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 _, @@ -473,7 +474,7 @@ impl MetalConnector { } ct }; - if ct.source.buffer_transform != Transform::None { + if ct.source.buffer_transform != ct.target.output_transform { // Rotations and mirroring are not supported. return None; } @@ -489,11 +490,15 @@ impl MetalConnector { 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))); ( - (ct.target.x1 + 1.0) * plane_w / 2.0, - (ct.target.x2 + 1.0) * plane_w / 2.0, - (ct.target.y1 + 1.0) * plane_h / 2.0, - (ct.target.y2 + 1.0) * plane_h / 2.0, + (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); @@ -594,6 +599,7 @@ impl MetalConnector { output.global.preferred_scale.get(), render_hw_cursor, output.has_fullscreen(), + output.global.transform.get(), ); let try_direct_scanout = try_direct_scanout && self.direct_scanout_enabled() @@ -719,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/gfx_api.rs b/src/gfx_api.rs index cc7e07ec..0672a0dd 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -9,7 +9,7 @@ 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, @@ -86,24 +86,44 @@ pub struct FramebufferRect { 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, width: f32, height: f32) -> Self { + 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; - [[x2, y1], [x1, y1], [x2, y2], [x1, 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 { @@ -136,7 +156,7 @@ 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>); @@ -164,13 +184,23 @@ impl dyn GfxFramebuffer { self.render(ops, Some(&Color { r, g, b, a })); } - pub fn renderer_base<'a>(&self, ops: &'a mut Vec, scale: Scale) -> RendererBase<'a> { - let (width, height) = self.size(); + 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 _, } @@ -179,7 +209,7 @@ impl dyn GfxFramebuffer { 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 = self.renderer_base(&mut ops, scale); + 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); @@ -192,7 +222,7 @@ impl dyn GfxFramebuffer { f: &mut dyn FnMut(&mut RendererBase), ) { let mut ops = self.take_render_ops(); - let mut renderer = self.renderer_base(&mut ops, scale); + let mut renderer = self.renderer_base(&mut ops, scale, Transform::None); f(&mut renderer); self.render(ops, clear); } @@ -206,15 +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: self.renderer_base(&mut ops, scale), + 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 { @@ -272,6 +305,7 @@ impl dyn GfxFramebuffer { scale, render_hardware_cursor, node.has_fullscreen(), + node.global.transform.get(), ) } @@ -284,6 +318,7 @@ impl dyn GfxFramebuffer { scale: Scale, render_hardware_cursor: bool, black_background: bool, + transform: Transform, ) { let pass = self.create_render_pass( node, @@ -293,19 +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: self.renderer_base(&mut ops, scale), + 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)); diff --git a/src/gfx_apis/gl/renderer/framebuffer.rs b/src/gfx_apis/gl/renderer/framebuffer.rs index 7a381619..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) } diff --git a/src/gfx_apis/vulkan/image.rs b/src/gfx_apis/vulkan/image.rs index 6bcdaef2..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 _) } diff --git a/src/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index d2b94113..77541294 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -181,6 +181,7 @@ impl JayScreencast { x_off, y_off, size, + on.global.transform.get(), ); self.client.event(Ready { self_id: self.id, @@ -217,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; @@ -241,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, @@ -492,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 8f804aaf..dbe8a9e8 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -19,10 +19,12 @@ use { clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedList, + transform_ext::TransformExt, }, wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id}, }, ahash::AHashMap, + jay_config::video::Transform, std::{ cell::{Cell, RefCell}, collections::hash_map::Entry, @@ -73,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, @@ -100,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, @@ -120,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), } } @@ -240,6 +252,7 @@ impl WlOutputGlobal { mem, *stride, wl_buffer.format, + Transform::None, ); } else { let fb = match wl_buffer.famebuffer.get() { @@ -258,6 +271,7 @@ impl WlOutputGlobal { x_off - capture.rect.x1(), y_off - capture.rect.y1(), size, + Transform::None, ); } if capture.with_damage.get() { @@ -269,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); @@ -314,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 b951f5c3..b8f34d71 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -81,6 +81,7 @@ const INVALID_SIZE: u32 = 2; const OFFSET_SINCE: u32 = 5; const BUFFER_SCALE_SINCE: u32 = 6; +const TRANSFORM_SINCE: u32 = 6; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum SurfaceRole { @@ -117,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, @@ -350,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() { @@ -442,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 { diff --git a/src/renderer.rs b/src/renderer.rs index e0901707..dcb2fd0b 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -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 { diff --git a/src/renderer/renderer_base.rs b/src/renderer/renderer_base.rs index 1c1b6f8e..9d935d3b 100644 --- a/src/renderer/renderer_base.rs +++ b/src/renderer/renderer_base.rs @@ -6,6 +6,7 @@ use { theme::Color, utils::transform_ext::TransformExt, }, + jay_config::video::Transform, std::rc::Rc, }; @@ -14,6 +15,7 @@ pub struct RendererBase<'a> { pub scaled: bool, pub scale: Scale, pub scalef: f64, + pub transform: Transform, pub fb_width: f32, pub fb_height: f32, } @@ -78,6 +80,7 @@ impl RendererBase<'_> { (bx.y1() + dy) as f32, (bx.x2() + dx) as f32, (bx.y2() + dy) as f32, + self.transform, self.fb_width, self.fb_height, ), @@ -109,6 +112,7 @@ impl RendererBase<'_> { y1 + dy, x2 + dx, y2 + dy, + self.transform, self.fb_width, self.fb_height, ), @@ -158,6 +162,7 @@ impl RendererBase<'_> { target_y[0] as f32, target_x[1] as f32, target_y[1] as f32, + self.transform, self.fb_width, self.fb_height, ), 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 ede10acf..aa535e21 100644 --- a/src/state.rs +++ b/src/state.rs @@ -17,7 +17,7 @@ use { fixed::Fixed, forker::ForkerProxy, format::Format, - gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, + gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture, SampleRect}, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, WaylandGlobal}, ifs::{ @@ -27,6 +27,7 @@ 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}, @@ -65,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}, @@ -149,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 { @@ -767,19 +772,30 @@ impl State { 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: target.renderer_base(&mut ops, Scale::from_int(1)), + 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::from_int(1), 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() { @@ -817,13 +833,15 @@ impl State { 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(); + || 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; @@ -853,6 +871,7 @@ impl State { x_off - capture.rect.x1(), y_off - capture.rect.y1(), size, + transform, ); mem.access(|mem| { fb.copy_to_shm( diff --git a/src/tree/output.rs b/src/tree/output.rs index 4d7e7ce5..b5ea0216 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -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}, @@ -386,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); + 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_mode.width, old_mode.height) != (mode.width, mode.height) { + 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() { @@ -411,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();