pub mod device;
pub mod egl_gl;
pub mod framebuffer;
use crate::multimedia::video::Nv12DmaBufFrame;
use crate::render::device::DrmCard;
use crate::render::egl_gl::EglGlContext;
use crate::render::framebuffer::Framebuffer;
use anyhow::Context;
use gbm::{AsRaw, BufferObject};
use khronos_egl::EGLDisplay;
use log::info;
use skia_safe::gpu::gl::{Format, FramebufferInfo, Interface, TextureInfo};
use skia_safe::gpu::{
BackendRenderTarget, ContextOptions, DirectContext, Mipmapped, Protected, SurfaceOrigin,
backend_render_targets, backend_textures, direct_contexts, surfaces,
};
use skia_safe::{AlphaType, ColorType, Image, Surface};
use std::collections::HashMap;
use std::ffi::{CStr, c_void};
use std::ops::Deref;
use std::rc::Rc;
use std::sync::Arc;
const DRM_FORMAT_MOD_LINEAR: u64 = 0;
const EGL_LINUX_DMA_BUF_EXT: u32 = 0x3270;
const EGL_LINUX_DRM_FOURCC_EXT: i32 = 0x3271;
const DRM_FORMAT_NV12: i32 = 0x3231564E;
const EGL_WIDTH: i32 = 0x3057;
const EGL_HEIGHT: i32 = 0x3056;
const EGL_NONE: i32 = 0x3038;
const EGL_DMA_BUF_PLANE0_FD_EXT: i32 = 0x3272;
const EGL_DMA_BUF_PLANE0_OFFSET_EXT: i32 = 0x3273;
const EGL_DMA_BUF_PLANE0_PITCH_EXT: i32 = 0x3274;
const EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT: i32 = 0x3443;
const EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT: i32 = 0x3444;
const EGL_DMA_BUF_PLANE1_FD_EXT: i32 = 0x3275;
const EGL_DMA_BUF_PLANE1_OFFSET_EXT: i32 = 0x3276;
const EGL_DMA_BUF_PLANE1_PITCH_EXT: i32 = 0x3277;
const EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT: i32 = 0x3445;
const EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT: i32 = 0x3446;
const GL_TEXTURE_EXTERNAL_OES: u32 = 0x8D65;
const GL_TEXTURE_MIN_FILTER: u32 = 0x2801;
const GL_TEXTURE_MAG_FILTER: u32 = 0x2800;
const GL_LINEAR: i32 = 0x2601;
type EglCreateImageKHR = unsafe extern "system" fn(
*mut c_void,
*mut c_void,
u32,
*mut c_void,
*const i32,
) -> *mut c_void;
type EglDestroyImageKHR = unsafe extern "system" fn(*mut c_void, *mut c_void) -> u32;
type GlEGLImageTargetTexture2DOES = unsafe extern "system" fn(u32, *mut c_void);
type GlGenTextures = unsafe extern "system" fn(i32, *mut u32);
type GlBindTexture = unsafe extern "system" fn(u32, u32);
type GlTexParameteri = unsafe extern "system" fn(u32, u32, i32);
type GlDeleteTextures = unsafe extern "system" fn(i32, *const u32);
pub struct GlEglProcs {
pub egl_create_image_khr: EglCreateImageKHR,
pub egl_destroy_image_khr: EglDestroyImageKHR,
pub gl_egl_image_target_texture_2d_oes: GlEGLImageTargetTexture2DOES,
pub gl_gen_textures: GlGenTextures,
pub gl_bind_texture: GlBindTexture,
pub gl_tex_parameteri: GlTexParameteri,
pub gl_delete_textures: GlDeleteTextures,
}
pub struct SkiaNv12Frame {
egl_destroy_image_khr: EglDestroyImageKHR,
gl_delete_textures: GlDeleteTextures,
tex_id: u32,
egl_display: EGLDisplay,
egl_image: *mut c_void,
#[allow(unused)]
pub raw_fb: Nv12DmaBufFrame,
i: Image,
}
pub trait GetTime {
fn get_time(&self) -> i64;
fn get_gen(&self) -> u64;
}
impl GetTime for SkiaNv12Frame {
fn get_time(&self) -> i64 {
self.raw_fb.timestamp_ms
}
fn get_gen(&self) -> u64 {
self.raw_fb.generation
}
}
impl GetTime for Option<SkiaNv12Frame> {
fn get_time(&self) -> i64 {
if let Some(k) = self {
return k.raw_fb.timestamp_ms;
}
-1
}
fn get_gen(&self) -> u64 {
if let Some(k) = self {
return k.raw_fb.generation;
}
0
}
}
impl Deref for SkiaNv12Frame {
type Target = Image;
fn deref(&self) -> &Self::Target {
&self.i
}
}
impl Drop for SkiaNv12Frame {
fn drop(&mut self) {
unsafe {
(self.gl_delete_textures)(1, &self.tex_id);
(self.egl_destroy_image_khr)(self.egl_display, self.egl_image);
}
}
}
pub struct Renderer {
pub surface: Surface,
#[allow(unused)]
rt: BackendRenderTarget,
pub context: DirectContext,
fb_cache: HashMap<u32, Rc<Framebuffer<DrmCard>>>,
pub egl: EglGlContext,
pub gbm_surface: gbm::Surface<()>,
#[allow(unused)]
gbm: gbm::Device<Arc<DrmCard>>,
d: Arc<DrmCard>,
procs: GlEglProcs,
}
impl Renderer {
pub fn new(d: Arc<DrmCard>, width: i32, height: i32) -> anyhow::Result<Self> {
let modifiers = [DRM_FORMAT_MOD_LINEAR];
let modifiers_iter = modifiers.iter().map(|&m| gbm::Modifier::from(m));
let gbm = gbm::Device::new(d.clone()).context("gbm::Device::new")?;
let gbm_surface = gbm
.create_surface_with_modifiers2::<()>(
width as u32,
height as u32,
gbm::Format::Argb8888,
modifiers_iter,
gbm::BufferObjectFlags::SCANOUT | gbm::BufferObjectFlags::RENDERING,
)
.context("gbm create_surface")?;
let egl = unsafe {
EglGlContext::new(
gbm.as_raw() as *mut c_void,
gbm_surface.as_raw() as *mut c_void,
)?
};
let procs = unsafe {
let get_proc = |name: &[u8]| -> *const c_void {
egl.egl
.get_proc_address(CStr::from_bytes_with_nul_unchecked(name).to_str().unwrap())
.unwrap() as *const c_void
};
GlEglProcs {
egl_create_image_khr: std::mem::transmute::<
*const libc::c_void,
unsafe extern "system" fn(
*mut libc::c_void,
*mut libc::c_void,
u32,
*mut libc::c_void,
*const i32,
) -> *mut libc::c_void,
>(get_proc(b"eglCreateImageKHR\0")),
egl_destroy_image_khr: std::mem::transmute::<
*const libc::c_void,
unsafe extern "system" fn(*mut libc::c_void, *mut libc::c_void) -> u32,
>(get_proc(b"eglDestroyImageKHR\0")),
gl_egl_image_target_texture_2d_oes: std::mem::transmute::<
*const libc::c_void,
unsafe extern "system" fn(u32, *mut libc::c_void),
>(get_proc(
b"glEGLImageTargetTexture2DOES\0",
)),
gl_gen_textures: std::mem::transmute::<
*const libc::c_void,
unsafe extern "system" fn(i32, *mut u32),
>(get_proc(b"glGenTextures\0")),
gl_bind_texture: std::mem::transmute::<
*const libc::c_void,
unsafe extern "system" fn(u32, u32),
>(get_proc(b"glBindTexture\0")),
gl_tex_parameteri: std::mem::transmute::<
*const libc::c_void,
unsafe extern "system" fn(u32, u32, i32),
>(get_proc(b"glTexParameteri\0")),
gl_delete_textures: std::mem::transmute::<
*const libc::c_void,
unsafe extern "system" fn(i32, *const u32),
>(get_proc(b"glDeleteTextures\0")),
}
};
let interface =
Interface::new_load_with(|name| egl.egl.get_proc_address(name).unwrap() as *const _)
.expect("Failed to create GL interface");
let options = ContextOptions::default();
let mut context = direct_contexts::make_gl(interface, Some(&options))
.expect("Fail to Create Skia DirectContext");
let fb_info = FramebufferInfo {
fboid: 0,
format: Format::RGBA8.into(),
protected: Protected::No,
};
let backend_render_target = backend_render_targets::make_gl(
(width, height),
None, 0, fb_info,
);
let surface = surfaces::wrap_backend_render_target(
&mut context,
&backend_render_target,
SurfaceOrigin::BottomLeft,
ColorType::RGBA8888,
None,
None,
)
.expect("Fail to create Skia Surface");
Ok(Self {
context,
rt: backend_render_target,
surface,
gbm,
gbm_surface,
egl,
procs,
fb_cache: HashMap::new(),
d,
})
}
pub fn swap(&mut self) -> anyhow::Result<(Rc<Framebuffer<DrmCard>>, BufferObject<()>)> {
self.context
.flush_and_submit_surface(&mut self.surface, None);
self.egl.swap_buffers()?;
let current_bo = unsafe { self.gbm_surface.lock_front_buffer() }
.context("gbm_surface.lock_front_buffer")?;
let handle = unsafe { current_bo.handle().u32_ };
if !self.fb_cache.contains_key(&handle) {
let fb = Framebuffer::create(self.d.clone(), ¤t_bo)?;
info!("Cache miss: created new Framebuffer for handle {}", handle);
self.fb_cache.insert(handle, Rc::new(fb));
}
let fb = self.fb_cache.get(&handle).context("Cache exception")?;
Ok((fb.to_owned(), current_bo))
}
pub fn import_nv12_frame(&mut self, f: Nv12DmaBufFrame) -> anyhow::Result<SkiaNv12Frame> {
unsafe {
let procs = &self.procs;
let modifier_lo = (f.format_modifier & 0xFFFFFFFF) as i32;
let modifier_hi = (f.format_modifier >> 32) as i32;
let attribs = [
EGL_WIDTH,
f.width,
EGL_HEIGHT,
f.height,
EGL_LINUX_DRM_FOURCC_EXT,
DRM_FORMAT_NV12,
EGL_DMA_BUF_PLANE0_FD_EXT,
f.fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT,
f.offset_y as i32,
EGL_DMA_BUF_PLANE0_PITCH_EXT,
f.pitch_y as i32,
EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT,
modifier_lo,
EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT,
modifier_hi,
EGL_DMA_BUF_PLANE1_FD_EXT,
f.fd,
EGL_DMA_BUF_PLANE1_OFFSET_EXT,
f.offset_uv as i32,
EGL_DMA_BUF_PLANE1_PITCH_EXT,
f.pitch_uv as i32,
EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT,
modifier_lo,
EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT,
modifier_hi,
EGL_NONE,
];
let egl_display = self.egl.display.as_ptr();
let egl_image = (procs.egl_create_image_khr)(
egl_display,
std::ptr::null_mut(), EGL_LINUX_DMA_BUF_EXT,
std::ptr::null_mut(),
attribs.as_ptr(),
);
anyhow::ensure!(
!egl_image.is_null(),
"eglCreateImageKHR failed to import DMA-BUF"
);
let mut tex_id = 0;
(procs.gl_gen_textures)(1, &mut tex_id);
(procs.gl_bind_texture)(GL_TEXTURE_EXTERNAL_OES, tex_id);
(procs.gl_tex_parameteri)(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
(procs.gl_tex_parameteri)(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
(procs.gl_egl_image_target_texture_2d_oes)(GL_TEXTURE_EXTERNAL_OES, egl_image);
let texture_info = TextureInfo {
target: GL_TEXTURE_EXTERNAL_OES,
id: tex_id,
format: Format::RGBA8.into(),
protected: Protected::No,
};
let backend_texture =
backend_textures::make_gl((f.width, f.height), Mipmapped::No, texture_info, "0");
let imported_image = Image::from_texture(
&mut self.context,
&backend_texture,
SurfaceOrigin::TopLeft,
ColorType::RGBA8888,
AlphaType::Premul,
None,
)
.context("Failed to create Skia Image from BackendTexture")?;
Ok(SkiaNv12Frame {
egl_destroy_image_khr: procs.egl_destroy_image_khr,
gl_delete_textures: procs.gl_delete_textures,
tex_id,
egl_display,
egl_image,
raw_fb: f,
i: imported_image,
})
}
}
pub fn resize(&mut self, width: i32, height: i32) -> anyhow::Result<()> {
self.context
.flush_and_submit_surface(&mut self.surface, None);
self.fb_cache.clear();
self.egl
.egl
.make_current(self.egl.display, None, None, Some(self.egl.context))
.context("eglMakeCurrent unbind surface failed")?;
self.egl
.egl
.destroy_surface(self.egl.display, self.egl.surface)
.context("eglDestroySurface failed")?;
let modifiers = [DRM_FORMAT_MOD_LINEAR];
let modifiers_iter = modifiers.iter().map(|&m| gbm::Modifier::from(m));
self.gbm_surface = self
.gbm
.create_surface_with_modifiers2::<()>(
width as u32,
height as u32,
gbm::Format::Argb8888,
modifiers_iter,
gbm::BufferObjectFlags::SCANOUT | gbm::BufferObjectFlags::RENDERING,
)
.context("gbm create_surface resize")?;
self.egl.surface = unsafe {
self.egl.egl.create_window_surface(
self.egl.display,
self.egl.config,
self.gbm_surface.as_raw() as *mut c_void,
None,
)
}
.context("eglCreateWindowSurface resize")?;
self.egl
.egl
.make_current(
self.egl.display,
Some(self.egl.surface),
Some(self.egl.surface),
Some(self.egl.context),
)
.context("eglMakeCurrent bind new surface failed")?;
let fb_info = FramebufferInfo {
fboid: 0,
format: Format::RGBA8.into(),
protected: Protected::No,
};
self.rt = backend_render_targets::make_gl((width, height), None, 0, fb_info);
self.surface = surfaces::wrap_backend_render_target(
&mut self.context,
&self.rt,
SurfaceOrigin::BottomLeft,
ColorType::RGBA8888,
None,
None,
)
.context("Fail to recreate Skia Surface on resize")?;
Ok(())
}
}