diff --git a/wayvr/src/overlays/screen/backend.rs b/wayvr/src/overlays/screen/backend.rs index 92b6ba3f..732ab42f 100644 --- a/wayvr/src/overlays/screen/backend.rs +++ b/wayvr/src/overlays/screen/backend.rs @@ -57,11 +57,14 @@ pub struct ScreenBackend { stereo: Option, stereo_full_frame: bool, stereo_adjust_mouse: bool, + crop_rect: [f32; 4], + crop_dirty: bool, pub(super) logical_pos: Vec2, pub(super) logical_size: Vec2, pub(super) mouse_transform_original: Transform, mouse_transform_override: MouseTransform, just_resumed: bool, + source_meta: Option, } impl ScreenBackend { @@ -87,17 +90,22 @@ impl ScreenBackend { }, stereo_full_frame: false, stereo_adjust_mouse: false, + crop_rect: [0.0, 0.0, 1.0, 1.0], + crop_dirty: false, logical_pos: Vec2::ZERO, logical_size: Vec2::ZERO, mouse_transform_original: Transform::Undefined, mouse_transform_override: MouseTransform::Default, just_resumed: false, + source_meta: None, } } pub(super) fn apply_mouse_transform_with_override(&mut self, override_transform: Transform) { - let mut size = self.logical_size; - let pos = self.logical_pos; + let crop_origin = Vec2::new(self.crop_rect[0], self.crop_rect[1]); + let crop_size = Vec2::new(self.crop_rect[2], self.crop_rect[3]); + let mut size = self.logical_size * crop_size; + let pos = self.logical_pos + self.logical_size * crop_origin; if self.stereo_adjust_mouse && let Some(stereo) = self.stereo.as_ref() @@ -153,6 +161,64 @@ impl ScreenBackend { ), }; } + + fn effective_extent(&self, source_extent: [u32; 2]) -> [u32; 2] { + [ + (source_extent[0] as f32 * self.crop_rect[2]) + .round() + .max(1.0) as u32, + (source_extent[1] as f32 * self.crop_rect[3]) + .round() + .max(1.0) as u32, + ] + } + + fn apply_meta_state( + &mut self, + app: &mut AppState, + mut source_meta: FrameMeta, + stereo: StereoMode, + ) -> anyhow::Result<()> { + let source_extent = source_meta.extent; + let effective_extent = self.effective_extent(source_extent); + source_meta.extent = effective_extent; + + if let Some(pipeline) = self.pipeline.as_mut() { + if self + .meta + .is_some_and(|old| old.extent != source_meta.extent) + { + pipeline.set_extent( + app, + [source_meta.extent[0] as f32, source_meta.extent[1] as f32], + [0.0, 0.0], + [source_extent[0] as f32, source_extent[1] as f32], + )?; + self.interaction_transform = Some(ui_transform(source_meta.extent)); + } + if self.crop_dirty { + pipeline.set_crop_rect(self.crop_rect)?; + } + pipeline.ensure_stereo(stereo); + } else { + let pipeline = ScreenPipeline::new( + &source_meta, + source_extent, + app, + stereo, + [0.0, 0.0], + self.crop_rect, + )?; + self.pipeline = Some(pipeline); + self.interaction_transform = Some(ui_transform(source_meta.extent)); + } + + self.meta = Some(source_meta); + self.crop_dirty = false; + let frame_transform = mouse_transform_to_transform(self.mouse_transform_override); + self.apply_mouse_transform_with_override(frame_transform); + Ok(()) + } } impl OverlayBackend for ScreenBackend { @@ -237,26 +303,19 @@ impl OverlayBackend for ScreenBackend { } } - if let Some(pipeline) = self.pipeline.as_mut() { - if self.meta.is_some_and(|old| old.extent != meta.extent) { - pipeline.set_extent( - app, - [meta.extent[0] as _, meta.extent[1] as _], - [0., 0.], - )?; - self.interaction_transform = Some(ui_transform(meta.extent)); - } - } else { - let pipeline = ScreenPipeline::new(&meta, app, stereo, [0., 0.])?; - self.pipeline = Some(pipeline); - self.interaction_transform = Some(ui_transform(meta.extent)); - } - - self.meta = Some(meta); + self.source_meta = Some(meta); + self.apply_meta_state(app, meta, stereo)?; self.cur_frame = Some(frame); Ok(ShouldRender::Should) } else if self.cur_frame.is_some() { + if self.crop_dirty + && let Some(source_meta) = self.source_meta + { + let stereo = self.stereo.unwrap_or(StereoMode::None); + self.apply_meta_state(app, source_meta, stereo)?; + return Ok(ShouldRender::Should); + } if self.just_resumed { self.just_resumed = false; Ok(ShouldRender::Should) @@ -364,6 +423,7 @@ impl OverlayBackend for ScreenBackend { BackendAttrib::StereoAdjustMouse => Some(BackendAttribValue::StereoAdjustMouse( self.stereo_adjust_mouse, )), + BackendAttrib::CropRect => Some(BackendAttribValue::CropRect(self.crop_rect)), _ => None, } } @@ -397,6 +457,13 @@ impl OverlayBackend for ScreenBackend { self.apply_mouse_transform_with_override(frame_transform); true } + BackendAttribValue::CropRect(new) => { + self.crop_rect = new; + self.crop_dirty = true; + let frame_transform = mouse_transform_to_transform(self.mouse_transform_override); + self.apply_mouse_transform_with_override(frame_transform); + true + } _ => false, } } diff --git a/wayvr/src/overlays/screen/capture.rs b/wayvr/src/overlays/screen/capture.rs index a37e4b42..b3b8aa46 100644 --- a/wayvr/src/overlays/screen/capture.rs +++ b/wayvr/src/overlays/screen/capture.rs @@ -54,18 +54,23 @@ pub struct ScreenPipeline { pass: SmallVec<[BufPass; 2]>, pipeline: Arc>, extentf: [f32; 2], + source_extentf: [f32; 2], offsetf: [f32; 2], + crop_rect: [f32; 4], stereo: StereoMode, } impl ScreenPipeline { pub fn new( meta: &FrameMeta, + source_extent: [u32; 2], app: &mut AppState, stereo: StereoMode, offsetf: [f32; 2], + crop_rect: [f32; 4], ) -> anyhow::Result { let extentf = [meta.extent[0] as f32, meta.extent[1] as f32]; + let source_extentf = [source_extent[0] as f32, source_extent[1] as f32]; let pipeline = app.gfx.create_pipeline( app.gfx_extras.shaders.get("vert_quad").unwrap(), // want panic @@ -80,7 +85,9 @@ impl ScreenPipeline { mouse: Self::create_mouse_pass(app, pipeline.clone(), extentf, offsetf)?, pipeline, extentf, + source_extentf, offsetf, + crop_rect, stereo, }; me.ensure_stereo(stereo); @@ -110,8 +117,10 @@ impl ScreenPipeline { self.pass.pop(); } + let stereo = self.stereo; + let crop_rect = self.crop_rect; for (eye, current) in self.pass.iter_mut().enumerate() { - let verts = stereo_mode_to_verts(self.stereo, eye); + let verts = cropped_stereo_mode_to_verts(stereo, crop_rect, eye); current.buf_vert.write()?.copy_from_slice(&verts); } Ok(()) @@ -122,8 +131,10 @@ impl ScreenPipeline { app: &mut AppState, extentf: [f32; 2], offsetf: [f32; 2], + source_extentf: [f32; 2], ) -> anyhow::Result<()> { self.extentf = extentf; + self.source_extentf = source_extentf; self.offsetf = offsetf; self.pass.clear(); @@ -131,6 +142,16 @@ impl ScreenPipeline { Ok(()) } + pub fn set_crop_rect(&mut self, crop_rect: [f32; 4]) -> anyhow::Result<()> { + self.crop_rect = crop_rect; + let stereo = self.stereo; + for (eye, current) in self.pass.iter_mut().enumerate() { + let verts = cropped_stereo_mode_to_verts(stereo, crop_rect, eye); + current.buf_vert.write()?.copy_from_slice(&verts); + } + Ok(()) + } + fn create_pass( app: &mut AppState, pipeline: Arc>, @@ -220,15 +241,26 @@ impl ScreenPipeline { cmd_buf.run_ref(¤t.pass)?; if let Some(mouse) = mouse.as_ref() { - let size = CURSOR_SIZE * self.extentf[1]; + let [crop_x, crop_y, crop_w, crop_h] = self.crop_rect; + if mouse.x < crop_x + || mouse.x > crop_x + crop_w + || mouse.y < crop_y + || mouse.y > crop_y + crop_h + { + continue; + } + + let local_mouse_x = (mouse.x - crop_x) / crop_w; + let local_mouse_y = (mouse.y - crop_y) / crop_h; + let size = CURSOR_SIZE * self.source_extentf[1] / crop_h.max(0.01); let half_size = size * 0.5; upload_quad_vertices( &mut self.mouse.buf_vert, self.extentf[0], self.extentf[1], - mouse.x.mul_add(self.extentf[0], -half_size), - mouse.y.mul_add(self.extentf[1], -half_size), + local_mouse_x.mul_add(self.extentf[0], -half_size), + local_mouse_y.mul_add(self.extentf[1], -half_size), size, size, )?; @@ -241,6 +273,21 @@ impl ScreenPipeline { } } +fn cropped_stereo_mode_to_verts( + stereo: StereoMode, + crop_rect: [f32; 4], + array_index: usize, +) -> [Vert2Uv; 4] { + let [crop_x, crop_y, crop_w, crop_h] = crop_rect; + stereo_mode_to_verts(stereo, array_index).map(|vert| Vert2Uv { + in_pos: vert.in_pos, + in_uv: [ + crop_x + vert.in_uv[0] * crop_w, + crop_y + vert.in_uv[1] * crop_h, + ], + }) +} + fn stereo_mode_to_verts(stereo: StereoMode, array_index: usize) -> [Vert2Uv; 4] { let eye = match stereo { StereoMode::RightLeft | StereoMode::BottomTop => (1 - array_index) as f32, diff --git a/wayvr/src/overlays/wayvr.rs b/wayvr/src/overlays/wayvr.rs index 2b0b0274..a0ebe5a2 100644 --- a/wayvr/src/overlays/wayvr.rs +++ b/wayvr/src/overlays/wayvr.rs @@ -352,6 +352,7 @@ impl OverlayBackend for WvrWindowBackend { app, [inner_extent[0] as _, inner_extent[1] as _], [BORDER_SIZE as _, (BAR_SIZE + BORDER_SIZE) as _], + [inner_extent[0] as _, inner_extent[1] as _], )?; self.apply_extent(app, &meta)?; self.inner_extent = inner_extent; @@ -359,9 +360,11 @@ impl OverlayBackend for WvrWindowBackend { } else { let pipeline = ScreenPipeline::new( &meta, + inner_extent, app, self.stereo.unwrap_or(StereoMode::None), [BORDER_SIZE as _, (BAR_SIZE + BORDER_SIZE) as _], + [0.0, 0.0, 1.0, 1.0], )?; self.apply_extent(app, &meta)?; self.pipeline = Some(pipeline); diff --git a/wlx-common/src/overlays.rs b/wlx-common/src/overlays.rs index 63490e71..763dc733 100644 --- a/wlx-common/src/overlays.rs +++ b/wlx-common/src/overlays.rs @@ -25,6 +25,7 @@ pub enum BackendAttrib { StereoFullFrame, StereoAdjustMouse, MouseTransform, + CropRect, Icon, } @@ -34,6 +35,7 @@ pub enum BackendAttribValue { StereoFullFrame(bool), StereoAdjustMouse(bool), MouseTransform(MouseTransform), + CropRect([f32; 4]), #[serde(skip_serializing, skip_deserializing)] Icon(Arc), } @@ -45,6 +47,7 @@ impl BackendAttribValue { Self::StereoFullFrame(val) => !*val, Self::StereoAdjustMouse(val) => !*val, Self::MouseTransform(val) => *val == MouseTransform::default(), + Self::CropRect(val) => *val == [0.0, 0.0, 1.0, 1.0], Self::Icon(_) => false, } }