From 97e8d487a06ba81e7141c874b8ef3faced18f1bb Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 28 May 2022 22:04:00 +0200 Subject: [PATCH] wayland: add support for NV12 format --- src/format.rs | 17 +++ src/ifs/wl_shm.rs | 10 +- src/ifs/wl_shm_pool.rs | 2 +- src/ifs/zwp_linux_dmabuf_v1.rs | 6 + src/render.rs | 8 ++ src/render/egl/display.rs | 42 ++++--- src/render/egl/image.rs | 1 + src/render/ext.rs | 9 +- src/render/gl/render_buffer.rs | 3 + src/render/gl/sys.rs | 1 + src/render/gl/texture.rs | 109 +++++------------- src/render/renderer/context.rs | 49 ++++++-- src/render/renderer/renderer.rs | 26 +++-- .../shaders/tex-external-alpha.frag.glsl | 9 ++ src/render/shaders/tex-external.frag.glsl | 9 ++ 15 files changed, 182 insertions(+), 119 deletions(-) create mode 100644 src/render/shaders/tex-external-alpha.frag.glsl create mode 100644 src/render/shaders/tex-external.frag.glsl diff --git a/src/format.rs b/src/format.rs index d7fc1d30..5e09e918 100644 --- a/src/format.rs +++ b/src/format.rs @@ -16,7 +16,9 @@ pub struct Format { pub gl_type: GLint, pub drm: u32, pub wl_id: Option, + pub external_only_guess: bool, pub has_alpha: bool, + pub shm_supported: bool, } static FORMATS_MAP: Lazy> = Lazy::new(|| { @@ -72,7 +74,9 @@ pub static FORMATS: &[Format] = &[ gl_type: GL_UNSIGNED_BYTE, drm: ARGB8888_DRM, wl_id: Some(ARGB8888_ID), + external_only_guess: false, has_alpha: true, + shm_supported: true, }, Format { name: "xrgb8888", @@ -81,7 +85,20 @@ pub static FORMATS: &[Format] = &[ gl_type: GL_UNSIGNED_BYTE, drm: XRGB8888_DRM, wl_id: Some(XRGB8888_ID), + external_only_guess: false, has_alpha: false, + shm_supported: true, + }, + Format { + name: "nv12", + bpp: 1, // wrong but only used for shm + gl_format: 0, // wrong but only used for shm + gl_type: GL_UNSIGNED_BYTE, // wrong but only used for shm + drm: fourcc_code('N', 'V', '1', '2'), + wl_id: None, + external_only_guess: true, + has_alpha: false, + shm_supported: false, }, // Format { // id: fourcc_code('C', '8', ' ', ' '), diff --git a/src/ifs/wl_shm.rs b/src/ifs/wl_shm.rs index 2222e816..4841712d 100644 --- a/src/ifs/wl_shm.rs +++ b/src/ifs/wl_shm.rs @@ -44,10 +44,12 @@ impl WlShmGlobal { track!(client, obj); client.add_client_obj(&obj)?; for format in FORMATS { - client.event(Format { - self_id: id, - format: format.wl_id.unwrap_or(format.drm), - }); + if format.shm_supported { + client.event(Format { + self_id: id, + format: format.wl_id.unwrap_or(format.drm), + }); + } } Ok(()) } diff --git a/src/ifs/wl_shm_pool.rs b/src/ifs/wl_shm_pool.rs index 0d888c16..3830ec46 100644 --- a/src/ifs/wl_shm_pool.rs +++ b/src/ifs/wl_shm_pool.rs @@ -45,7 +45,7 @@ impl WlShmPool { let req: CreateBuffer = self.client.parse(self, parser)?; let drm_format = map_wayland_format_id(req.format); let format = match formats().get(&drm_format) { - Some(f) => *f, + Some(f) if f.shm_supported => *f, _ => return Err(WlShmPoolError::InvalidFormat(req.format)), }; if req.height < 0 || req.width < 0 || req.stride < 0 || req.offset < 0 { diff --git a/src/ifs/zwp_linux_dmabuf_v1.rs b/src/ifs/zwp_linux_dmabuf_v1.rs index be59412a..6bf80a79 100644 --- a/src/ifs/zwp_linux_dmabuf_v1.rs +++ b/src/ifs/zwp_linux_dmabuf_v1.rs @@ -38,9 +38,15 @@ impl ZwpLinuxDmabufV1Global { if let Some(ctx) = client.state.render_ctx.get() { let formats = ctx.formats(); for format in formats.values() { + if format.implicit_external_only && !ctx.supports_external_texture() { + continue; + } obj.send_format(format.format.drm); if version >= MODIFIERS_SINCE_VERSION { for modifier in format.modifiers.values() { + if modifier.external_only && !ctx.supports_external_texture() { + continue; + } obj.send_modifier(format.format.drm, modifier.modifier); } } diff --git a/src/render.rs b/src/render.rs index 2514c651..92ee0b2f 100644 --- a/src/render.rs +++ b/src/render.rs @@ -88,4 +88,12 @@ pub enum RenderError { XRGB888, #[error("The DRM device does not have a render node")] NoRenderNode, + #[error("The requested format is not supported")] + UnsupportedFormat, + #[error("The requested modifier is not supported")] + UnsupportedModifier, + #[error("Image is external only and cannot be rendered to")] + ExternalOnly, + #[error("OpenGL context does not support external textures")] + ExternalUnsupported, } diff --git a/src/render/egl/display.rs b/src/render/egl/display.rs index da6abd79..b136a3e7 100644 --- a/src/render/egl/display.rs +++ b/src/render/egl/display.rs @@ -38,6 +38,7 @@ use { #[derive(Debug)] pub struct EglFormat { pub format: &'static Format, + pub implicit_external_only: bool, pub modifiers: AHashMap, } @@ -142,6 +143,13 @@ impl EglDisplay { } pub fn import_dmabuf(self: &Rc, buf: &DmaBuf) -> Result, RenderError> { + let format = match self.formats.get(&buf.format.drm) { + Some(fmt) => match fmt.modifiers.get(&buf.modifier) { + Some(fmt) => fmt, + _ => return Err(RenderError::UnsupportedModifier), + }, + _ => return Err(RenderError::UnsupportedFormat), + }; struct PlaneKey { fd: EGLint, offset: EGLint, @@ -212,6 +220,7 @@ impl EglDisplay { img, width: buf.width, height: buf.height, + external_only: format.external_only, })) } } @@ -243,11 +252,13 @@ unsafe fn query_formats(dpy: EGLDisplay) -> Result, Ren let formats = formats(); for fmt in vec { if let Some(format) = formats.get(&(fmt as u32)) { + let (modifiers, external_only) = query_modifiers(dpy, fmt, format)?; res.insert( format.drm, EglFormat { format: *format, - modifiers: query_modifiers(dpy, fmt)?, + implicit_external_only: external_only, + modifiers, }, ); } @@ -257,14 +268,15 @@ unsafe fn query_formats(dpy: EGLDisplay) -> Result, Ren unsafe fn query_modifiers( dpy: EGLDisplay, - format: EGLint, -) -> Result, RenderError> { + gl_format: EGLint, + format: &'static Format, +) -> Result<(AHashMap, bool), RenderError> { let mut mods = vec![]; let mut ext_only = vec![]; let mut num = 0; let res = PROCS.eglQueryDmaBufModifiersEXT( dpy, - format, + gl_format, num, ptr::null_mut(), ptr::null_mut(), @@ -277,7 +289,7 @@ unsafe fn query_modifiers( ext_only.reserve_exact(num as usize); let res = PROCS.eglQueryDmaBufModifiersEXT( dpy, - format, + gl_format, num, mods.as_mut_ptr(), ext_only.as_mut_ptr(), @@ -289,13 +301,6 @@ unsafe fn query_modifiers( mods.set_len(num as usize); ext_only.set_len(num as usize); let mut res = AHashMap::new(); - res.insert( - INVALID_MODIFIER, - EglModifier { - modifier: INVALID_MODIFIER, - external_only: false, - }, - ); for (modifier, ext_only) in mods.iter().copied().zip(ext_only.iter().copied()) { res.insert( modifier as _, @@ -305,5 +310,16 @@ unsafe fn query_modifiers( }, ); } - Ok(res) + let mut external_only = format.external_only_guess; + if res.len() > 0 { + external_only = res.values().any(|f| f.external_only); + } + res.insert( + INVALID_MODIFIER, + EglModifier { + modifier: INVALID_MODIFIER, + external_only, + }, + ); + Ok((res, external_only)) } diff --git a/src/render/egl/image.rs b/src/render/egl/image.rs index 0b8b8988..4746e9db 100644 --- a/src/render/egl/image.rs +++ b/src/render/egl/image.rs @@ -12,6 +12,7 @@ pub struct EglImage { pub img: EGLImageKHR, pub width: i32, pub height: i32, + pub external_only: bool, } impl Drop for EglImage { diff --git a/src/render/ext.rs b/src/render/ext.rs index cae4fd35..f42c3c74 100644 --- a/src/render/ext.rs +++ b/src/render/ext.rs @@ -118,11 +118,18 @@ pub(super) unsafe fn get_display_ext(dpy: EGLDisplay) -> DisplayExt { bitflags::bitflags! { pub struct GlExt: u32 { const GL_OES_EGL_IMAGE = 1 << 0; + const GL_OES_EGL_IMAGE_EXTERNAL = 1 << 1; } } pub fn get_gl_ext() -> GlExt { - let map = [("GL_OES_EGL_image", GlExt::GL_OES_EGL_IMAGE)]; + let map = [ + ("GL_OES_EGL_image", GlExt::GL_OES_EGL_IMAGE), + ( + "GL_OES_EGL_image_external", + GlExt::GL_OES_EGL_IMAGE_EXTERNAL, + ), + ]; match unsafe { get_extensions(glGetString(GL_EXTENSIONS) as _) } { Some(exts) => get_typed_ext(&exts, GlExt::empty(), &map), _ => GlExt::empty(), diff --git a/src/render/gl/render_buffer.rs b/src/render/gl/render_buffer.rs index c053086e..7e45eec2 100644 --- a/src/render/gl/render_buffer.rs +++ b/src/render/gl/render_buffer.rs @@ -26,6 +26,9 @@ impl GlRenderBuffer { img: &Rc, ctx: &Rc, ) -> Result, RenderError> { + if img.external_only { + return Err(RenderError::ExternalOnly); + } let mut rbo = 0; glGenRenderbuffers(1, &mut rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo); diff --git a/src/render/gl/sys.rs b/src/render/gl/sys.rs index ac91f620..006ce516 100644 --- a/src/render/gl/sys.rs +++ b/src/render/gl/sys.rs @@ -30,6 +30,7 @@ pub const GL_RENDERBUFFER: GLenum = 0x8D41; pub const GL_SCISSOR_TEST: GLenum = 0x0C11; pub const GL_TEXTURE0: GLenum = 0x84C0; pub const GL_TEXTURE_2D: GLenum = 0x0DE1; +pub const GL_TEXTURE_EXTERNAL_OES: GLenum = 0x8D65; #[allow(dead_code)] pub const GL_TEXTURE_MAG_FILTER: GLenum = 0x2800; pub const GL_TEXTURE_MIN_FILTER: GLenum = 0x2801; diff --git a/src/render/gl/texture.rs b/src/render/gl/texture.rs index 7f75d8f3..e488eac2 100644 --- a/src/render/gl/texture.rs +++ b/src/render/gl/texture.rs @@ -3,22 +3,17 @@ use { format::Format, render::{ egl::{context::EglContext, image::EglImage, PROCS}, - gl::{ - frame_buffer::GlFrameBuffer, - sys::{ - glBindFramebuffer, glBindTexture, glCheckFramebufferStatus, glDeleteTextures, - glFramebufferTexture2D, glGenFramebuffers, glGenTextures, glPixelStorei, - glTexImage2D, glTexParameteri, GLint, GLuint, GL_CLAMP_TO_EDGE, - GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER, GL_FRAMEBUFFER_COMPLETE, GL_LINEAR, - GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, - GL_TEXTURE_WRAP_T, GL_UNPACK_ROW_LENGTH_EXT, - }, + ext::GlExt, + gl::sys::{ + glBindTexture, glDeleteTextures, glGenTextures, glPixelStorei, glTexImage2D, + glTexParameteri, GLint, GLuint, GL_CLAMP_TO_EDGE, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, + GL_TEXTURE_WRAP_T, GL_UNPACK_ROW_LENGTH_EXT, }, - sys::GLeglImageOES, + sys::{GLeglImageOES, GLenum, GL_TEXTURE_EXTERNAL_OES}, RenderError, }, }, - std::{cell::Cell, ptr, rc::Rc}, + std::{cell::Cell, rc::Rc}, }; pub struct GlTexture { @@ -27,84 +22,30 @@ pub struct GlTexture { pub tex: GLuint, pub width: i32, pub height: i32, + pub external_only: bool, +} + +pub fn image_target(external_only: bool) -> GLenum { + match external_only { + true => GL_TEXTURE_EXTERNAL_OES, + false => GL_TEXTURE_2D, + } } impl GlTexture { - #[allow(dead_code)] - pub fn new( - ctx: &Rc, - format: &'static Format, - width: i32, - height: i32, - ) -> Result, RenderError> { - let tex = ctx.with_current(|| unsafe { - let mut tex = 0; - glGenTextures(1, &mut tex); - glBindTexture(GL_TEXTURE_2D, tex); - glTexImage2D( - GL_TEXTURE_2D, - 0, - format.gl_format, - width, - height, - 0, - format.gl_format as _, - format.gl_type as _, - ptr::null(), - ); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glBindTexture(GL_TEXTURE_2D, 0); - Ok(tex) - })?; - Ok(Rc::new(GlTexture { - ctx: ctx.clone(), - img: None, - tex, - width, - height, - })) - } - - #[allow(dead_code)] - pub unsafe fn to_framebuffer(self: &Rc) -> Result, RenderError> { - self.ctx.with_current(|| { - let mut fbo = 0; - glGenFramebuffers(1, &mut fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D( - GL_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, - self.tex, - 0, - ); - let fb = GlFrameBuffer { - _rb: None, - _tex: Some(self.clone()), - ctx: self.ctx.clone(), - fbo, - width: self.width, - height: self.height, - }; - let status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - if status != GL_FRAMEBUFFER_COMPLETE { - return Err(RenderError::CreateFramebuffer); - } - Ok(Rc::new(fb)) - }) - } - pub fn import_img(ctx: &Rc, img: &Rc) -> Result { + if !ctx.ext.contains(GlExt::GL_OES_EGL_IMAGE_EXTERNAL) { + return Err(RenderError::ExternalUnsupported); + } + let target = image_target(img.external_only); let tex = ctx.with_current(|| unsafe { let mut tex = 0; glGenTextures(1, &mut tex); - glBindTexture(GL_TEXTURE_2D, tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - PROCS.glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, GLeglImageOES(img.img.0)); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(target, tex); + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + PROCS.glEGLImageTargetTexture2DOES(target, GLeglImageOES(img.img.0)); + glBindTexture(target, 0); Ok(tex) })?; Ok(GlTexture { @@ -113,6 +54,7 @@ impl GlTexture { tex, width: img.width, height: img.height, + external_only: img.external_only, }) } @@ -155,6 +97,7 @@ impl GlTexture { tex, width, height, + external_only: false, }) } } diff --git a/src/render/renderer/context.rs b/src/render/renderer/context.rs index 895ce479..0be13b30 100644 --- a/src/render/renderer/context.rs +++ b/src/render/renderer/context.rs @@ -6,6 +6,7 @@ use { context::EglContext, display::{EglDisplay, EglFormat}, }, + ext::GlExt, gl::{ program::GlProgram, render_buffer::GlRenderBuffer, sys::GLint, texture::GlTexture, }, @@ -46,14 +47,19 @@ impl TexProg { } } +pub(super) struct TexProgs { + pub alpha: TexProg, + pub solid: TexProg, +} + pub struct RenderContext { pub(super) ctx: Rc, pub gbm: Rc, pub(super) render_node: Rc, - pub(super) tex_prog: TexProg, - pub(super) tex_alpha_prog: TexProg, + pub(super) tex_internal: TexProgs, + pub(super) tex_external: Option, pub(super) fill_prog: GlProgram, pub(super) fill_prog_pos: GLint, @@ -79,6 +85,10 @@ impl RenderContext { self.ctx.reset_status() } + pub fn supports_external_texture(&self) -> bool { + self.ctx.ext.contains(GlExt::GL_OES_EGL_IMAGE_EXTERNAL) + } + pub fn from_drm_device(drm: &Drm) -> Result { let nodes = drm.get_nodes()?; let node = match nodes @@ -97,16 +107,32 @@ impl RenderContext { } unsafe fn new(ctx: &Rc, node: &Rc) -> Result { - let tex_prog = GlProgram::from_shaders( - ctx, - include_str!("../shaders/tex.vert.glsl"), - include_str!("../shaders/tex.frag.glsl"), - )?; + let tex_vert = include_str!("../shaders/tex.vert.glsl"); + let tex_prog = + GlProgram::from_shaders(ctx, tex_vert, include_str!("../shaders/tex.frag.glsl"))?; let tex_alpha_prog = GlProgram::from_shaders( ctx, - include_str!("../shaders/tex.vert.glsl"), + tex_vert, include_str!("../shaders/tex-alpha.frag.glsl"), )?; + let tex_external = if ctx.ext.contains(GlExt::GL_OES_EGL_IMAGE_EXTERNAL) { + let solid = GlProgram::from_shaders( + ctx, + tex_vert, + include_str!("../shaders/tex-external.frag.glsl"), + )?; + let alpha = GlProgram::from_shaders( + ctx, + tex_vert, + include_str!("../shaders/tex-external-alpha.frag.glsl"), + )?; + Some(TexProgs { + alpha: TexProg::from(alpha), + solid: TexProg::from(solid), + }) + } else { + None + }; let fill_prog = GlProgram::from_shaders( ctx, include_str!("../shaders/fill.vert.glsl"), @@ -118,8 +144,11 @@ impl RenderContext { render_node: node.clone(), - tex_prog: TexProg::from(tex_prog), - tex_alpha_prog: TexProg::from(tex_alpha_prog), + tex_internal: TexProgs { + solid: TexProg::from(tex_prog), + alpha: TexProg::from(tex_alpha_prog), + }, + tex_external, fill_prog_pos: fill_prog.get_attrib_location(ustr!("pos")), fill_prog_color: fill_prog.get_uniform_location(ustr!("color")), diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index bb7df184..64ee16ae 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -17,9 +17,9 @@ use { glActiveTexture, glBindTexture, glDisableVertexAttribArray, glDrawArrays, glEnableVertexAttribArray, glTexParameteri, glUniform1i, glUniform4f, glUseProgram, glVertexAttribPointer, GL_FALSE, GL_FLOAT, GL_LINEAR, - GL_TEXTURE0, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_TRIANGLES, - GL_TRIANGLE_STRIP, + GL_TEXTURE0, GL_TEXTURE_MIN_FILTER, GL_TRIANGLES, GL_TRIANGLE_STRIP, }, + texture::image_target, }, renderer::context::RenderContext, sys::{glDisable, glEnable, GL_BLEND}, @@ -359,17 +359,29 @@ impl Renderer<'_> { unsafe { glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture.gl.tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + let target = image_target(texture.gl.external_only); + glBindTexture(target, texture.gl.tex); + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + let progs = match texture.gl.external_only { + true => match &self.ctx.tex_external { + Some(p) => p, + _ => { + log::error!("Trying to render an external-only texture but context does not support the required extension"); + return; + } + }, + false => &self.ctx.tex_internal, + }; let prog = match format.has_alpha { true => { glEnable(GL_BLEND); - &self.ctx.tex_alpha_prog + &progs.alpha } false => { glDisable(GL_BLEND); - &self.ctx.tex_prog + &progs.solid } }; @@ -423,7 +435,7 @@ impl Renderer<'_> { glDisableVertexAttribArray(prog.texcoord as _); glDisableVertexAttribArray(prog.pos as _); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(target, 0); } } diff --git a/src/render/shaders/tex-external-alpha.frag.glsl b/src/render/shaders/tex-external-alpha.frag.glsl new file mode 100644 index 00000000..580e31cd --- /dev/null +++ b/src/render/shaders/tex-external-alpha.frag.glsl @@ -0,0 +1,9 @@ +#extension GL_OES_EGL_image_external : require + +precision mediump float; +varying vec2 v_texcoord; +uniform samplerExternalOES tex; + +void main() { + gl_FragColor = texture2D(tex, v_texcoord); +} diff --git a/src/render/shaders/tex-external.frag.glsl b/src/render/shaders/tex-external.frag.glsl new file mode 100644 index 00000000..2c540b27 --- /dev/null +++ b/src/render/shaders/tex-external.frag.glsl @@ -0,0 +1,9 @@ +#extension GL_OES_EGL_image_external : require + +precision mediump float; +varying vec2 v_texcoord; +uniform samplerExternalOES tex; + +void main() { + gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0); +}