From 9fba5f9b4529658d8dd8de1644fc72d6d242e809 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 28 Feb 2024 15:59:20 +0100 Subject: [PATCH] render: support shm screencopy from direct scanout --- src/backends/metal/video.rs | 1 - src/format.rs | 7 +- src/gfx_api.rs | 8 ++ src/gfx_apis/gl/gl/render_buffer.rs | 55 +++++++++--- src/gfx_apis/gl/gl/sys.rs | 2 + src/gfx_apis/gl/renderer/context.rs | 13 +++ src/gfx_apis/gl/renderer/framebuffer.rs | 2 +- src/gfx_apis/vulkan.rs | 17 +++- src/ifs/jay_screencast.rs | 1 - src/ifs/wl_output.rs | 41 +++------ src/ifs/zwlr_screencopy_frame_v1.rs | 9 -- src/ifs/zwlr_screencopy_manager_v1.rs | 1 - src/state.rs | 108 ++++++++++++++++++++++-- 13 files changed, 203 insertions(+), 62 deletions(-) diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 441da79c..863b4f07 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -597,7 +597,6 @@ impl MetalConnector { output.has_fullscreen(), ); 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 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 72b5eeae..9c417662 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -415,6 +415,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/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 b9f78e66..b019e555 100644 --- a/src/gfx_apis/gl/renderer/context.rs +++ b/src/gfx_apis/gl/renderer/context.rs @@ -255,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 8c371137..7a381619 100644 --- a/src/gfx_apis/gl/renderer/framebuffer.rs +++ b/src/gfx_apis/gl/renderer/framebuffer.rs @@ -120,6 +120,6 @@ impl GfxFramebuffer for Framebuffer { } fn format(&self) -> &'static Format { - self.gl.rb.img.dmabuf.format + self.gl.rb.format } } diff --git a/src/gfx_apis/vulkan.rs b/src/gfx_apis/vulkan.rs index 17429782..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, }, @@ -255,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/ifs/jay_screencast.rs b/src/ifs/jay_screencast.rs index dcd01b69..d2b94113 100644 --- a/src/ifs/jay_screencast.rs +++ b/src/ifs/jay_screencast.rs @@ -176,7 +176,6 @@ impl JayScreencast { self.client.state.perform_screencopy( texture, &buffer.fb, - on.global.preferred_scale.get(), on.global.pos.get(), render_hardware_cursors, x_off, diff --git a/src/ifs/wl_output.rs b/src/ifs/wl_output.rs index ef9f5477..8f804aaf 100644 --- a/src/ifs/wl_output.rs +++ b/src/ifs/wl_output.rs @@ -18,7 +18,6 @@ use { buffd::{MsgParser, MsgParserError}, clonecell::CloneCell, copyhashmap::CopyHashMap, - errorfmt::ErrorFmt, linkedlist::LinkedList, }, wire::{wl_output::*, WlOutputId, ZxdgOutputV1Id}, @@ -201,10 +200,6 @@ 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, tex: &Rc, @@ -235,30 +230,17 @@ impl WlOutputGlobal { 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 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, + ); } else { let fb = match wl_buffer.famebuffer.get() { Some(fb) => fb, @@ -271,7 +253,6 @@ impl WlOutputGlobal { self.state.perform_screencopy( tex, &fb, - self.preferred_scale.get(), self.pos.get(), render_hardware_cursors, x_off - capture.rect.x1(), 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/state.rs b/src/state.rs index 279bbc5a..b226e3e2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,12 +9,14 @@ 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, + format::Format, gfx_api::{GfxContext, GfxError, GfxFramebuffer, GfxTexture}, gfx_apis::create_gfx_context, globals::{Globals, GlobalsError, WaylandGlobal}, @@ -30,6 +32,7 @@ use { 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, }, @@ -759,7 +762,6 @@ impl State { &self, src: &Rc, target: &Rc, - scale: Scale, position: Rect, render_hardware_cursors: bool, x_off: i32, @@ -771,9 +773,9 @@ impl State { let mut renderer = Renderer { base: RendererBase { ops: &mut ops, - scaled: scale != 1, - scale, - scalef: scale.to_f64(), + scaled: false, + scale: Scale::from_int(1), + scalef: 1.0, }, state: self, result: None, @@ -782,7 +784,7 @@ impl State { }; renderer .base - .render_texture(src, x_off, y_off, None, size, scale, None); + .render_texture(src, x_off, y_off, None, 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 +799,100 @@ 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, + ) { + 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(); + 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, + ); + 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(); + } + } }