zwlr_screencopy v3 support

This commit is contained in:
galister
2026-01-09 18:47:45 +09:00
parent e6e1764b36
commit 5e77bab588
20 changed files with 578 additions and 192 deletions

View File

@@ -178,7 +178,7 @@ impl WvrServerState {
// this will throw a compile-time error if smithay's drm-fourcc is out of sync with wlx-capture's
let mut formats: Vec<smithay::backend::allocator::Format> = vec![];
for f in &gfx_extras.drm_formats {
for f in &*gfx_extras.drm_formats {
formats.push(*f);
}

View File

@@ -10,11 +10,14 @@ use vulkano::{
VulkanError, VulkanObject,
device::Device,
format::Format,
image::{Image, ImageCreateInfo, ImageTiling, ImageUsage, SubresourceLayout, sys::RawImage},
image::{
Image, ImageCreateInfo, ImageMemory, ImageTiling, ImageType, ImageUsage, SubresourceLayout,
sys::RawImage, view::ImageView,
},
memory::{
DedicatedAllocation, DeviceMemory, ExternalMemoryHandleType, ExternalMemoryHandleTypes,
MemoryAllocateInfo, MemoryImportInfo, MemoryPropertyFlags, ResourceMemory,
allocator::{MemoryAllocator, MemoryTypeFilter},
allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter},
},
sync::Sharing,
};
@@ -299,6 +302,70 @@ pub(super) unsafe fn create_dmabuf_image(
}
}
pub struct ExportedDmabufImage {
pub view: Arc<ImageView>,
pub fd: std::fs::File,
pub offset: u32,
pub stride: i32,
pub modifier: DrmModifier,
}
pub fn export_dmabuf_image(
allocator: Arc<dyn MemoryAllocator>,
extent: [u32; 3],
format: Format,
modifier: DrmModifier,
) -> anyhow::Result<ExportedDmabufImage> {
let layout = SubresourceLayout {
offset: 0,
size: 0,
row_pitch: align_to(format.block_size() * (extent[0] as u64), 64),
array_pitch: None,
depth_pitch: None,
};
let image = Image::new(
allocator.clone(),
ImageCreateInfo {
image_type: ImageType::Dim2d,
format,
extent,
usage: ImageUsage::TRANSFER_DST | ImageUsage::TRANSFER_SRC | ImageUsage::SAMPLED,
tiling: ImageTiling::DrmFormatModifier,
drm_format_modifiers: vec![modifier.into()],
drm_format_modifier_plane_layouts: vec![layout],
external_memory_handle_types: ExternalMemoryHandleTypes::DMA_BUF,
..Default::default()
},
AllocationCreateInfo {
..Default::default()
},
)
.context("Could not create image to export")?;
let fd = match image.memory() {
ImageMemory::Normal(planes) if planes.len() == 1 => {
let plane = planes.first().unwrap();
plane
.device_memory()
.export_fd(ExternalMemoryHandleType::DmaBuf)?
}
_ => anyhow::bail!("Could not export DMA-buf: invalid ImageMemory"),
};
Ok(ExportedDmabufImage {
view: ImageView::new_default(image)?,
fd,
modifier: DrmModifier::from(modifier),
offset: layout.offset as _,
stride: layout.row_pitch as _,
})
}
fn align_to(value: u64, alignment: u64) -> u64 {
((value + alignment - 1) / alignment) * alignment
}
pub(super) fn get_drm_formats(device: Arc<Device>) -> Vec<DrmFormat> {
let possible_formats = [
DrmFourcc::Abgr8888,

View File

@@ -69,7 +69,7 @@ pub const BLEND_ALPHA: AttachmentBlend = AttachmentBlend {
pub struct WGfxExtras {
pub shaders: HashMap<&'static str, Arc<ShaderModule>>,
pub drm_formats: Vec<DrmFormat>,
pub drm_formats: Arc<[DrmFormat]>,
pub queue_capture: Option<Arc<Queue>>,
pub quad_verts: Vert2Buf,
pub fallback_image: Arc<ImageView>,
@@ -95,7 +95,7 @@ impl WGfxExtras {
let shader = frag_screen::load(gfx.device.clone())?;
shaders.insert("frag_screen", shader);
let drm_formats = get_drm_formats(gfx.device.clone());
let drm_formats = get_drm_formats(gfx.device.clone()).into();
let vertices = [
Vert2Uv {

View File

@@ -12,6 +12,7 @@ use crate::{
input::{HoverResult, PointerHit, PointerMode},
},
graphics::ExtentExt,
overlays::screen::capture::MyFirstDmaExporter,
state::AppState,
subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT, WheelDelta},
windowing::backend::{
@@ -41,8 +42,15 @@ fn set_next_move(millis_from_now: u64) {
);
}
pub(super) enum CaptureType {
PipeWire,
ScreenCopy,
Xshm,
}
pub struct ScreenBackend {
name: Arc<str>,
capture_type: CaptureType,
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
pipeline: Option<ScreenPipeline>,
cur_frame: Option<WlxCaptureOut>,
@@ -61,10 +69,12 @@ impl ScreenBackend {
pub(super) fn new_raw(
name: Arc<str>,
xr_backend: XrBackend,
capture_type: CaptureType,
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
) -> Self {
Self {
name,
capture_type,
capture,
pipeline: None,
cur_frame: None,
@@ -151,17 +161,17 @@ impl OverlayBackend for ScreenBackend {
let allow_dmabuf = !matches!(
capture_method,
CaptureMethod::PwFallback | CaptureMethod::ScreenCopy
CaptureMethod::PipeWireCpu | CaptureMethod::ScreenCopyCpu
);
let dmabuf_formats = if !supports_dmabuf {
let (dmabuf_formats, dma_exporter) = if !supports_dmabuf {
log::info!("Capture method does not support DMA-buf");
if app.gfx_extras.queue_capture.is_none() {
log::warn!(
"Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance."
);
}
&Vec::new()
([].as_slice(), None)
} else if !allow_dmabuf {
log::info!(
"Not using DMA-buf capture due to {}",
@@ -172,16 +182,25 @@ impl OverlayBackend for ScreenBackend {
"Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance."
);
}
&Vec::new()
([].as_slice(), None)
} else {
log::warn!(
"Using GPU capture. If you're having issues with screens, go to the Dashboard's Settings tab and switch 'Wayland capture method' to a CPU option!"
);
&app.gfx_extras.drm_formats
let dma_exporter = if matches!(self.capture_type, CaptureType::ScreenCopy) {
Some(MyFirstDmaExporter::new(
app.gfx.clone(),
app.gfx_extras.drm_formats.clone(),
))
} else {
None
};
(&*app.gfx_extras.drm_formats, dma_exporter)
};
let user_data = WlxCaptureIn::new(self.name.clone(), app);
let user_data = WlxCaptureIn::new(self.name.clone(), app, dma_exporter);
self.capture
.init(dmabuf_formats, user_data, receive_callback);
self.capture.request_new_frame();

View File

@@ -1,4 +1,8 @@
use std::{f32::consts::PI, sync::Arc};
use std::{
f32::consts::PI,
os::fd::AsRawFd,
sync::{Arc, OnceLock},
};
use glam::{Affine3A, Vec3};
use smallvec::{SmallVec, smallvec};
@@ -8,24 +12,30 @@ use vulkano::{
device::Queue,
format::Format,
image::{Image, sampler::Filter, view::ImageView},
memory::{ExternalMemoryHandleTypes, allocator::MemoryAllocator},
pipeline::graphics::color_blend::AttachmentBlend,
};
use wgui::gfx::{
WGfx,
cmd::WGfxClearMode,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
use wgui::{
gfx::{
WGfx,
cmd::WGfxClearMode,
memory_allocator,
pass::WGfxPass,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
},
log::LogErr,
};
use wlx_capture::{
DrmFormat, WlxCapture,
DrmFormat, DrmFourcc, DrmModifier, WlxCapture,
frame::{self as wlx_frame, FrameFormat, MouseMeta, WlxFrame},
wlr_screencopy::DmaExporter,
};
use wlx_common::{config::GeneralConfig, overlays::StereoMode};
use crate::{
graphics::{
Vert2Uv,
dmabuf::{WGfxDmabuf, fourcc_to_vk},
ExtentExt, Vert2Uv,
dmabuf::{ExportedDmabufImage, WGfxDmabuf, export_dmabuf_image, fourcc_to_vk},
upload_quad_vertices,
},
state::AppState,
@@ -343,15 +353,120 @@ fn stereo_mode_to_verts(stereo: StereoMode, array_index: usize) -> [Vert2Uv; 4]
}
}
#[derive(Clone)]
static DMA_ALLOCATOR: OnceLock<Arc<dyn MemoryAllocator>> = OnceLock::new();
pub(super) struct MyFirstDmaExporter {
gfx: Arc<WGfx>,
drm_formats: Arc<[DrmFormat]>,
images: SmallVec<[ExportedDmabufImage; 2]>,
fourcc: DrmFourcc,
current: usize,
}
impl MyFirstDmaExporter {
pub(super) fn new(gfx: Arc<WGfx>, drm_formats: Arc<[DrmFormat]>) -> Self {
Self {
gfx,
drm_formats,
images: smallvec![],
fourcc: DrmFourcc::Argb8888,
current: 0,
}
}
fn get_current(&self) -> Option<(Arc<ImageView>, FrameFormat)> {
let image = self.images.get(self.current)?;
let extent = image.view.extent_u32arr();
Some((
image.view.clone(),
FrameFormat {
width: extent[0],
height: extent[1],
drm_format: DrmFormat {
code: self.fourcc,
modifier: image.modifier,
},
transform: wlx_frame::Transform::Undefined,
},
))
}
fn set_format(
&mut self,
width: u32,
height: u32,
fourcc: wlx_capture::DrmFourcc,
) -> Option<()> {
if let Some(image) = self.images.first() {
let extent = image.view.image().extent();
if self.fourcc == fourcc && extent[0] == width && extent[1] == height {
return Some(());
}
}
self.images.clear();
let Some(modifier) = self
.drm_formats
.iter()
.filter(|f| f.code == fourcc)
.map(|f| f.modifier)
.next()
else {
log::error!("Unsupported format requested: {fourcc}");
return None;
};
let format = fourcc_to_vk(fourcc)
.log_err("Could not export new dmabuf due to invalid format")
.ok()?;
let allocator = DMA_ALLOCATOR.get_or_init(|| {
memory_allocator(
self.gfx.device.clone(),
Some(ExternalMemoryHandleTypes::DMA_BUF),
)
});
for _ in 0..2 {
let image =
export_dmabuf_image(allocator.clone(), [width, height, 1], format, modifier)
.log_err("Could not export DMA-buf image")
.ok()?;
self.images.push(image);
}
Some(())
}
fn next_frame(&mut self) -> Option<(wlx_frame::FramePlane, DrmModifier)> {
self.current = 1 - self.current;
let image = self.images.get(self.current)?;
Some((
wlx_frame::FramePlane {
fd: Some(image.fd.as_raw_fd()),
offset: image.offset,
stride: image.stride,
},
image.modifier,
))
}
}
pub struct WlxCaptureIn {
name: Arc<str>,
gfx: Arc<WGfx>,
queue: Arc<Queue>,
dma_exporter: Option<MyFirstDmaExporter>,
}
impl WlxCaptureIn {
pub(super) fn new(name: Arc<str>, app: &AppState) -> Self {
pub(super) fn new(
name: Arc<str>,
app: &AppState,
dma_exporter: Option<MyFirstDmaExporter>,
) -> Self {
Self {
name,
gfx: app.gfx.clone(),
@@ -361,10 +476,24 @@ impl WlxCaptureIn {
.as_ref()
.unwrap_or_else(|| &app.gfx.queue_xfer)
.clone(),
dma_exporter,
}
}
}
impl DmaExporter for WlxCaptureIn {
fn next_frame(
&mut self,
width: u32,
height: u32,
fourcc: DrmFourcc,
) -> Option<(wlx_frame::FramePlane, DrmModifier)> {
let dma_exporter = self.dma_exporter.as_mut()?;
dma_exporter.set_format(width, height, fourcc)?;
dma_exporter.next_frame()
}
}
#[derive(Clone)]
pub(super) struct WlxCaptureOut {
pub(super) image: Arc<ImageView>,
@@ -501,6 +630,33 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
mouse: frame.mouse,
})
}
WlxFrame::Implicit => {
log::trace!("{}: New Implicit frame", me.name);
let Some((image, format)) = me.dma_exporter.as_ref().unwrap().get_current() else {
log::error!("{}: Implicit frame is missing!", me.name);
return None;
};
Some(WlxCaptureOut {
image,
format,
mouse: None,
})
}
}
}
/// DmaExporter is not used for SHM capture
pub(super) struct DummyDrmExporter;
impl DmaExporter for DummyDrmExporter {
fn next_frame(
&mut self,
_: u32,
_: u32,
_: DrmFourcc,
) -> Option<(wlx_frame::FramePlane, DrmModifier)> {
unreachable!()
}
}
@@ -508,7 +664,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
// In this case, receive_callback needs to run on the main thread
pub(super) struct MainThreadWlxCapture<T>
where
T: WlxCapture<(), WlxFrame>,
T: WlxCapture<DummyDrmExporter, WlxFrame>,
{
inner: T,
data: Option<WlxCaptureIn>,
@@ -516,7 +672,7 @@ where
impl<T> MainThreadWlxCapture<T>
where
T: WlxCapture<(), WlxFrame>,
T: WlxCapture<DummyDrmExporter, WlxFrame>,
{
pub const fn new(inner: T) -> Self {
Self { inner, data: None }
@@ -525,7 +681,7 @@ where
impl<T> WlxCapture<WlxCaptureIn, WlxCaptureOut> for MainThreadWlxCapture<T>
where
T: WlxCapture<(), WlxFrame>,
T: WlxCapture<DummyDrmExporter, WlxFrame>,
{
fn init(
&mut self,
@@ -534,7 +690,8 @@ where
_: fn(&WlxCaptureIn, WlxFrame) -> Option<WlxCaptureOut>,
) {
self.data = Some(user_data);
self.inner.init(dmabuf_formats, (), receive_callback_dummy);
self.inner
.init(dmabuf_formats, DummyDrmExporter, receive_callback_dummy);
}
fn is_ready(&self) -> bool {
self.inner.is_ready()
@@ -559,7 +716,7 @@ where
}
#[allow(clippy::trivially_copy_pass_by_ref, clippy::unnecessary_wraps)]
const fn receive_callback_dummy(_: &(), frame: WlxFrame) -> Option<WlxFrame> {
const fn receive_callback_dummy(_: &DummyDrmExporter, frame: WlxFrame) -> Option<WlxFrame> {
Some(frame)
}

View File

@@ -22,7 +22,10 @@ use crate::{
input::{HoverResult, PointerHit},
task::{OverlayTask, TaskType, ToggleMode},
},
overlays::screen::capture::{MainThreadWlxCapture, new_wlx_capture},
overlays::screen::{
backend::CaptureType,
capture::{MainThreadWlxCapture, new_wlx_capture},
},
state::{AppSession, AppState},
subsystem::hid::WheelDelta,
windowing::{
@@ -94,6 +97,7 @@ impl OverlayBackend for MirrorBackend {
self.renderer = Some(ScreenBackend::new_raw(
self.name.clone(),
app.xr_backend,
CaptureType::PipeWire,
capture,
));
app.tasks

View File

@@ -25,6 +25,8 @@ impl ScreenBackend {
token: Option<&str>,
app: &mut AppState,
) -> anyhow::Result<(Self, Option<String> /* pipewire restore token */)> {
use crate::overlays::screen::backend::CaptureType;
let name = output.name.clone();
let embed_mouse = !app.session.config.double_cursor_fix;
@@ -57,7 +59,12 @@ impl ScreenBackend {
PipewireCapture::new(name, node_id)
);
Ok((
Self::new_raw(output.name.clone(), app.xr_backend, capture),
Self::new_raw(
output.name.clone(),
app.xr_backend,
CaptureType::PipeWire,
capture,
),
select_screen_result.restore_token,
))
}

View File

@@ -3,7 +3,6 @@ use wlx_capture::{
WlxCapture,
frame::Transform,
wayland::{WlxClient, WlxOutput},
wlr_dmabuf::WlrDmabufCapture,
wlr_screencopy::WlrScreencopyCapture,
};
use wlx_common::{
@@ -12,7 +11,7 @@ use wlx_common::{
};
use crate::{
overlays::screen::create_screen_from_backend,
overlays::screen::{backend::CaptureType, create_screen_from_backend},
state::{AppState, ScreenMeta},
};
@@ -24,22 +23,18 @@ use super::{
};
impl ScreenBackend {
pub fn new_wlr_dmabuf(output: &WlxOutput, app: &AppState) -> Option<Self> {
let client = WlxClient::new()?;
let capture = new_wlx_capture!(
app.gfx_extras.queue_capture,
WlrDmabufCapture::new(client, output.id)
);
Some(Self::new_raw(output.name.clone(), app.xr_backend, capture))
}
pub fn new_wlr_screencopy(output: &WlxOutput, app: &AppState) -> Option<Self> {
let client = WlxClient::new()?;
let capture = new_wlx_capture!(
app.gfx_extras.queue_capture,
WlrScreencopyCapture::new(client, output.id)
);
Some(Self::new_raw(output.name.clone(), app.xr_backend, capture))
Some(Self::new_raw(
output.name.clone(),
app.xr_backend,
CaptureType::ScreenCopy,
capture,
))
}
}
@@ -52,9 +47,12 @@ pub fn create_screen_renderer_wl(
) -> Option<ScreenBackend> {
let mut capture: Option<ScreenBackend> = None;
if matches!(app.session.config.capture_method, CaptureMethod::ScreenCopy) && has_wlr_screencopy
if matches!(
app.session.config.capture_method,
CaptureMethod::ScreenCopyCpu | CaptureMethod::ScreenCopyGpu | CaptureMethod::Auto
) && has_wlr_screencopy
{
log::info!("{}: Using Wlr Screencopy Wl-SHM", &output.name);
log::info!("{}: Using ScreenCopy capture", &output.name);
capture = ScreenBackend::new_wlr_screencopy(output, app);
}

View File

@@ -8,7 +8,7 @@ use wlx_capture::{
};
use crate::{
overlays::screen::create_screen_from_backend,
overlays::screen::{backend::CaptureType, create_screen_from_backend},
state::{AppState, ScreenMeta},
};
@@ -27,7 +27,12 @@ impl ScreenBackend {
app.gfx_extras.queue_capture,
XshmCapture::new(screen.clone())
);
Self::new_raw(screen.name.clone(), app.xr_backend, capture)
Self::new_raw(
screen.name.clone(),
app.xr_backend,
CaptureType::Xshm,
capture,
)
}
}
@@ -97,6 +102,7 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result<ScreenCreateDa
let mut backend = ScreenBackend::new_raw(
m.name.clone(),
app.xr_backend,
CaptureType::PipeWire,
new_wlx_capture!(
app.gfx_extras.queue_capture,
PipewireCapture::new(m.name.clone(), s.node_id)

View File

@@ -1,4 +1,3 @@
use chrono_tz::PST8PDT;
use glam::{Affine2, Affine3A, Quat, Vec2, Vec3, vec2, vec3};
use smithay::{
desktop::PopupManager,