2021-04-10 11:42:04 +00:00
|
|
|
// This example demonstrates how to output GL textures, within an
|
2019-04-02 16:58:39 +00:00
|
|
|
// EGL/X11 context provided by the application, and render those
|
|
|
|
// textures in the GL application.
|
2018-10-18 10:25:14 +00:00
|
|
|
|
|
|
|
// {videotestsrc} - { glsinkbin }
|
|
|
|
|
2020-12-20 15:09:22 +00:00
|
|
|
use gst::element_error;
|
2018-10-18 10:25:14 +00:00
|
|
|
use gst::prelude::*;
|
|
|
|
|
|
|
|
use gst_gl::prelude::*;
|
|
|
|
|
|
|
|
use std::ffi::CStr;
|
|
|
|
use std::mem;
|
|
|
|
use std::ptr;
|
2019-04-24 13:07:15 +00:00
|
|
|
use std::sync::mpsc;
|
2018-10-18 10:25:14 +00:00
|
|
|
|
2020-05-02 23:23:38 +00:00
|
|
|
use anyhow::Error;
|
|
|
|
use derive_more::{Display, Error};
|
2018-10-18 10:25:14 +00:00
|
|
|
|
|
|
|
#[path = "../examples-common.rs"]
|
|
|
|
mod examples_common;
|
|
|
|
|
2020-05-02 23:23:38 +00:00
|
|
|
#[derive(Debug, Display, Error)]
|
|
|
|
#[display(fmt = "Missing element {}", _0)]
|
|
|
|
struct MissingElement(#[error(not(source))] &'static str);
|
2018-10-18 10:25:14 +00:00
|
|
|
|
2020-05-02 23:23:38 +00:00
|
|
|
#[derive(Debug, Display, Error)]
|
|
|
|
#[display(fmt = "Received error from {}: {} (debug: {:?})", src, error, debug)]
|
2018-10-18 10:25:14 +00:00
|
|
|
struct ErrorMessage {
|
|
|
|
src: String,
|
|
|
|
error: String,
|
|
|
|
debug: Option<String>,
|
2020-05-02 23:23:38 +00:00
|
|
|
source: glib::Error,
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
2019-02-28 08:32:13 +00:00
|
|
|
#[rustfmt::skip]
|
2018-10-18 10:25:14 +00:00
|
|
|
static VERTICES: [f32; 20] = [
|
|
|
|
1.0, 1.0, 0.0, 1.0, 0.0,
|
|
|
|
-1.0, 1.0, 0.0, 0.0, 0.0,
|
|
|
|
-1.0, -1.0, 0.0, 0.0, 1.0,
|
|
|
|
1.0, -1.0, 0.0, 1.0, 1.0,
|
|
|
|
];
|
|
|
|
|
|
|
|
static INDICES: [u16; 6] = [0, 1, 2, 0, 2, 3];
|
|
|
|
|
2019-02-28 08:32:13 +00:00
|
|
|
#[rustfmt::skip]
|
2018-10-18 10:25:14 +00:00
|
|
|
static IDENTITY: [f32; 16] = [
|
|
|
|
1.0, 0.0, 0.0, 0.0,
|
|
|
|
0.0, 1.0, 0.0, 0.0,
|
|
|
|
0.0, 0.0, 1.0, 0.0,
|
|
|
|
0.0, 0.0, 0.0, 1.0,
|
|
|
|
];
|
|
|
|
|
2019-02-28 08:54:32 +00:00
|
|
|
const VS_SRC: &[u8] = b"
|
2018-10-18 10:25:14 +00:00
|
|
|
uniform mat4 u_transformation;
|
|
|
|
attribute vec4 a_position;
|
|
|
|
attribute vec2 a_texcoord;
|
|
|
|
varying vec2 v_texcoord;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
gl_Position = u_transformation * a_position;
|
|
|
|
v_texcoord = a_texcoord;
|
|
|
|
}
|
|
|
|
\0";
|
|
|
|
|
2019-02-28 08:54:32 +00:00
|
|
|
const FS_SRC: &[u8] = b"
|
2018-10-18 10:25:14 +00:00
|
|
|
#ifdef GL_ES
|
|
|
|
precision mediump float;
|
|
|
|
#endif
|
|
|
|
varying vec2 v_texcoord;
|
|
|
|
uniform sampler2D tex;
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
gl_FragColor = texture2D(tex, v_texcoord);
|
|
|
|
}
|
|
|
|
\0";
|
|
|
|
|
2019-02-28 08:54:32 +00:00
|
|
|
#[allow(clippy::unreadable_literal)]
|
|
|
|
#[allow(clippy::unused_unit)]
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
2020-07-28 10:26:17 +00:00
|
|
|
#[allow(clippy::manual_non_exhaustive)]
|
2018-10-18 10:25:14 +00:00
|
|
|
mod gl {
|
|
|
|
pub use self::Gles2 as Gl;
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/test_gl_bindings.rs"));
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Gl {
|
|
|
|
gl: gl::Gl,
|
|
|
|
program: gl::types::GLuint,
|
|
|
|
attr_position: gl::types::GLint,
|
|
|
|
attr_texture: gl::types::GLint,
|
2019-07-11 12:34:28 +00:00
|
|
|
vao: Option<gl::types::GLuint>,
|
2018-10-18 10:25:14 +00:00
|
|
|
vertex_buffer: gl::types::GLuint,
|
|
|
|
vbo_indices: gl::types::GLuint,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Gl {
|
|
|
|
fn draw_frame(&self, texture_id: gl::types::GLuint) {
|
|
|
|
unsafe {
|
|
|
|
// render
|
|
|
|
self.gl.ClearColor(0.0, 0.0, 0.0, 1.0);
|
|
|
|
self.gl.Clear(gl::COLOR_BUFFER_BIT);
|
|
|
|
|
|
|
|
self.gl.BlendColor(0.0, 0.0, 0.0, 1.0);
|
|
|
|
if self.gl.BlendFuncSeparate.is_loaded() {
|
|
|
|
self.gl.BlendFuncSeparate(
|
|
|
|
gl::SRC_ALPHA,
|
|
|
|
gl::CONSTANT_COLOR,
|
|
|
|
gl::ONE,
|
|
|
|
gl::ONE_MINUS_SRC_ALPHA,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
self.gl.BlendFunc(gl::SRC_ALPHA, gl::CONSTANT_COLOR);
|
|
|
|
}
|
|
|
|
self.gl.BlendEquation(gl::FUNC_ADD);
|
|
|
|
self.gl.Enable(gl::BLEND);
|
|
|
|
|
|
|
|
self.gl.UseProgram(self.program);
|
|
|
|
|
|
|
|
if self.gl.BindVertexArray.is_loaded() {
|
2019-07-11 12:34:28 +00:00
|
|
|
self.gl.BindVertexArray(self.vao.unwrap());
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
self.gl
|
|
|
|
.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.vbo_indices);
|
|
|
|
self.gl.BindBuffer(gl::ARRAY_BUFFER, self.vertex_buffer);
|
|
|
|
|
|
|
|
// Load the vertex position
|
|
|
|
self.gl.VertexAttribPointer(
|
|
|
|
self.attr_position as gl::types::GLuint,
|
|
|
|
3,
|
|
|
|
gl::FLOAT,
|
|
|
|
gl::FALSE,
|
|
|
|
(5 * mem::size_of::<f32>()) as gl::types::GLsizei,
|
|
|
|
ptr::null(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Load the texture coordinate
|
|
|
|
self.gl.VertexAttribPointer(
|
|
|
|
self.attr_texture as gl::types::GLuint,
|
|
|
|
2,
|
|
|
|
gl::FLOAT,
|
|
|
|
gl::FALSE,
|
|
|
|
(5 * mem::size_of::<f32>()) as gl::types::GLsizei,
|
|
|
|
(3 * mem::size_of::<f32>()) as *const () as *const _,
|
|
|
|
);
|
|
|
|
|
|
|
|
self.gl.EnableVertexAttribArray(self.attr_position as _);
|
|
|
|
self.gl.EnableVertexAttribArray(self.attr_texture as _);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.gl.ActiveTexture(gl::TEXTURE0);
|
|
|
|
self.gl.BindTexture(gl::TEXTURE_2D, texture_id);
|
|
|
|
|
|
|
|
let location = self
|
|
|
|
.gl
|
|
|
|
.GetUniformLocation(self.program, b"tex\0".as_ptr() as *const _);
|
|
|
|
self.gl.Uniform1i(location, 0);
|
|
|
|
|
|
|
|
let location = self
|
|
|
|
.gl
|
|
|
|
.GetUniformLocation(self.program, b"u_transformation\0".as_ptr() as *const _);
|
|
|
|
self.gl
|
|
|
|
.UniformMatrix4fv(location, 1, gl::FALSE, IDENTITY.as_ptr() as *const _);
|
|
|
|
|
|
|
|
self.gl
|
|
|
|
.DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_SHORT, ptr::null());
|
|
|
|
|
|
|
|
self.gl.BindTexture(gl::TEXTURE_2D, 0);
|
|
|
|
self.gl.UseProgram(0);
|
|
|
|
|
|
|
|
if self.gl.BindVertexArray.is_loaded() {
|
|
|
|
self.gl.BindVertexArray(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
self.gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
self.gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
self.gl.DisableVertexAttribArray(self.attr_position as _);
|
|
|
|
self.gl.DisableVertexAttribArray(self.attr_texture as _);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn resize(&self, size: glutin::dpi::PhysicalSize) {
|
|
|
|
unsafe {
|
|
|
|
self.gl
|
|
|
|
.Viewport(0, 0, size.width as i32, size.height as i32);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
|
2018-10-18 10:25:14 +00:00
|
|
|
let gl = gl::Gl::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _);
|
|
|
|
|
|
|
|
let version = unsafe {
|
|
|
|
let data = CStr::from_ptr(gl.GetString(gl::VERSION) as *const _)
|
|
|
|
.to_bytes()
|
|
|
|
.to_vec();
|
|
|
|
String::from_utf8(data).unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
println!("OpenGL version {}", version);
|
|
|
|
|
|
|
|
let (program, attr_position, attr_texture, vao, vertex_buffer, vbo_indices) = unsafe {
|
|
|
|
let vs = gl.CreateShader(gl::VERTEX_SHADER);
|
|
|
|
gl.ShaderSource(vs, 1, [VS_SRC.as_ptr() as *const _].as_ptr(), ptr::null());
|
|
|
|
gl.CompileShader(vs);
|
|
|
|
|
|
|
|
let fs = gl.CreateShader(gl::FRAGMENT_SHADER);
|
|
|
|
gl.ShaderSource(fs, 1, [FS_SRC.as_ptr() as *const _].as_ptr(), ptr::null());
|
|
|
|
gl.CompileShader(fs);
|
|
|
|
|
|
|
|
let program = gl.CreateProgram();
|
|
|
|
gl.AttachShader(program, vs);
|
|
|
|
gl.AttachShader(program, fs);
|
|
|
|
gl.LinkProgram(program);
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut success: gl::types::GLint = 1;
|
|
|
|
gl.GetProgramiv(fs, gl::LINK_STATUS, &mut success);
|
|
|
|
assert!(success != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
let attr_position = gl.GetAttribLocation(program, b"a_position\0".as_ptr() as *const _);
|
|
|
|
let attr_texture = gl.GetAttribLocation(program, b"a_texcoord\0".as_ptr() as *const _);
|
|
|
|
|
2019-07-11 12:34:28 +00:00
|
|
|
let vao = if gl.BindVertexArray.is_loaded() {
|
|
|
|
let mut vao = mem::MaybeUninit::uninit();
|
|
|
|
gl.GenVertexArrays(1, vao.as_mut_ptr());
|
|
|
|
let vao = vao.assume_init();
|
2018-10-18 10:25:14 +00:00
|
|
|
gl.BindVertexArray(vao);
|
2019-07-11 12:34:28 +00:00
|
|
|
Some(vao)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut vertex_buffer = mem::MaybeUninit::uninit();
|
|
|
|
gl.GenBuffers(1, vertex_buffer.as_mut_ptr());
|
|
|
|
let vertex_buffer = vertex_buffer.assume_init();
|
2018-10-18 10:25:14 +00:00
|
|
|
gl.BindBuffer(gl::ARRAY_BUFFER, vertex_buffer);
|
|
|
|
gl.BufferData(
|
|
|
|
gl::ARRAY_BUFFER,
|
|
|
|
(VERTICES.len() * mem::size_of::<f32>()) as gl::types::GLsizeiptr,
|
|
|
|
VERTICES.as_ptr() as *const _,
|
|
|
|
gl::STATIC_DRAW,
|
|
|
|
);
|
|
|
|
|
2019-07-11 12:34:28 +00:00
|
|
|
let mut vbo_indices = mem::MaybeUninit::uninit();
|
|
|
|
gl.GenBuffers(1, vbo_indices.as_mut_ptr());
|
|
|
|
let vbo_indices = vbo_indices.assume_init();
|
2018-10-18 10:25:14 +00:00
|
|
|
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vbo_indices);
|
|
|
|
gl.BufferData(
|
|
|
|
gl::ELEMENT_ARRAY_BUFFER,
|
|
|
|
(INDICES.len() * mem::size_of::<u16>()) as gl::types::GLsizeiptr,
|
|
|
|
INDICES.as_ptr() as *const _,
|
|
|
|
gl::STATIC_DRAW,
|
|
|
|
);
|
|
|
|
|
|
|
|
if gl.BindVertexArray.is_loaded() {
|
|
|
|
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, vbo_indices);
|
|
|
|
gl.BindBuffer(gl::ARRAY_BUFFER, vertex_buffer);
|
|
|
|
|
|
|
|
// Load the vertex position
|
|
|
|
gl.VertexAttribPointer(
|
|
|
|
attr_position as gl::types::GLuint,
|
|
|
|
3,
|
|
|
|
gl::FLOAT,
|
|
|
|
gl::FALSE,
|
|
|
|
(5 * mem::size_of::<f32>()) as gl::types::GLsizei,
|
|
|
|
ptr::null(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Load the texture coordinate
|
|
|
|
gl.VertexAttribPointer(
|
|
|
|
attr_texture as gl::types::GLuint,
|
|
|
|
2,
|
|
|
|
gl::FLOAT,
|
|
|
|
gl::FALSE,
|
|
|
|
(5 * mem::size_of::<f32>()) as gl::types::GLsizei,
|
|
|
|
(3 * mem::size_of::<f32>()) as *const () as *const _,
|
|
|
|
);
|
|
|
|
|
|
|
|
gl.EnableVertexAttribArray(attr_position as _);
|
|
|
|
gl.EnableVertexAttribArray(attr_texture as _);
|
|
|
|
|
|
|
|
gl.BindVertexArray(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
|
|
|
gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
|
|
|
|
|
|
|
(
|
|
|
|
program,
|
|
|
|
attr_position,
|
|
|
|
attr_texture,
|
|
|
|
vao,
|
|
|
|
vertex_buffer,
|
|
|
|
vbo_indices,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
Gl {
|
2019-02-28 08:54:32 +00:00
|
|
|
gl,
|
2018-10-18 10:25:14 +00:00
|
|
|
program,
|
|
|
|
attr_position,
|
|
|
|
attr_texture,
|
|
|
|
vao,
|
|
|
|
vertex_buffer,
|
|
|
|
vbo_indices,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct App {
|
|
|
|
pipeline: gst::Pipeline,
|
|
|
|
appsink: gst_app::AppSink,
|
2019-03-22 17:39:15 +00:00
|
|
|
glupload: gst::Element,
|
2018-10-18 10:25:14 +00:00
|
|
|
bus: gst::Bus,
|
2019-04-24 13:07:15 +00:00
|
|
|
events_loop: glutin::EventsLoop,
|
|
|
|
windowed_context: glutin::WindowedContext<glutin::PossiblyCurrent>,
|
2019-03-22 17:39:15 +00:00
|
|
|
shared_context: gst_gl::GLContext,
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl App {
|
|
|
|
fn new() -> Result<App, Error> {
|
|
|
|
gst::init()?;
|
|
|
|
|
2019-03-22 17:39:15 +00:00
|
|
|
let (pipeline, appsink, glupload) = App::create_pipeline()?;
|
2018-10-18 10:25:14 +00:00
|
|
|
let bus = pipeline
|
|
|
|
.get_bus()
|
|
|
|
.expect("Pipeline without bus. Shouldn't happen!");
|
|
|
|
|
|
|
|
let events_loop = glutin::EventsLoop::new();
|
|
|
|
let window = glutin::WindowBuilder::new().with_title("GL rendering");
|
2019-04-24 13:07:15 +00:00
|
|
|
let windowed_context = glutin::ContextBuilder::new()
|
|
|
|
.with_vsync(true)
|
|
|
|
.build_windowed(window, &events_loop)?;
|
2019-03-22 17:39:15 +00:00
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, err)| err)? };
|
2019-04-09 17:13:28 +00:00
|
|
|
|
2020-11-26 22:46:15 +00:00
|
|
|
#[cfg(any(feature = "gst-gl-x11", feature = "gst-gl-wayland"))]
|
2019-04-24 13:07:15 +00:00
|
|
|
let inner_window = windowed_context.window();
|
2019-02-27 18:58:47 +00:00
|
|
|
|
2019-03-22 17:39:15 +00:00
|
|
|
let shared_context: gst_gl::GLContext;
|
2019-02-27 18:58:47 +00:00
|
|
|
if cfg!(target_os = "linux") {
|
|
|
|
use glutin::os::unix::RawHandle;
|
2020-11-26 22:46:15 +00:00
|
|
|
#[cfg(any(feature = "gst-gl-x11", feature = "gst-gl-wayland"))]
|
2019-04-09 17:13:28 +00:00
|
|
|
use glutin::os::unix::WindowExt;
|
2019-02-27 18:58:47 +00:00
|
|
|
use glutin::os::ContextTraitExt;
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
let api = App::map_gl_api(windowed_context.get_api());
|
2019-02-27 18:58:47 +00:00
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
let (gl_context, gl_display, platform) = match unsafe { windowed_context.raw_handle() }
|
|
|
|
{
|
2020-11-26 22:46:15 +00:00
|
|
|
#[cfg(any(feature = "gst-gl-egl", feature = "gst-gl-wayland"))]
|
2019-04-02 16:58:39 +00:00
|
|
|
RawHandle::Egl(egl_context) => {
|
2020-11-26 22:46:15 +00:00
|
|
|
cfg_if::cfg_if! {
|
|
|
|
if #[cfg(feature = "gst-gl-egl")] {
|
|
|
|
let gl_display = if let Some(display) =
|
|
|
|
unsafe { windowed_context.get_egl_display() }
|
|
|
|
{
|
|
|
|
unsafe { gst_gl_egl::GLDisplayEGL::with_egl_display(display as usize) }.unwrap()
|
|
|
|
} else {
|
|
|
|
panic!("EGL context without EGL display");
|
|
|
|
};
|
|
|
|
} else if #[cfg(feature = "gst-gl-wayland")] {
|
|
|
|
let gl_display = if let Some(display) = inner_window.get_wayland_display() {
|
|
|
|
unsafe { gst_gl_wayland::GLDisplayWayland::with_display(display as usize) }.unwrap()
|
|
|
|
} else {
|
|
|
|
panic!("Wayland window without Wayland display");
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2019-04-09 17:13:28 +00:00
|
|
|
|
2019-04-02 16:58:39 +00:00
|
|
|
(
|
|
|
|
egl_context as usize,
|
|
|
|
gl_display.upcast::<gst_gl::GLDisplay>(),
|
|
|
|
gst_gl::GLPlatform::EGL,
|
|
|
|
)
|
|
|
|
}
|
2020-11-26 22:46:15 +00:00
|
|
|
#[cfg(feature = "gst-gl-x11")]
|
2019-04-02 16:58:39 +00:00
|
|
|
RawHandle::Glx(glx_context) => {
|
|
|
|
let gl_display = if let Some(display) = inner_window.get_xlib_display() {
|
2020-11-26 22:46:15 +00:00
|
|
|
unsafe { gst_gl_x11::GLDisplayX11::with_display(display as usize) }.unwrap()
|
2019-04-02 16:58:39 +00:00
|
|
|
} else {
|
|
|
|
panic!("X11 window without X Display");
|
|
|
|
};
|
|
|
|
|
|
|
|
(
|
|
|
|
glx_context as usize,
|
|
|
|
gl_display.upcast::<gst_gl::GLDisplay>(),
|
|
|
|
gst_gl::GLPlatform::GLX,
|
|
|
|
)
|
|
|
|
}
|
2019-09-07 10:08:09 +00:00
|
|
|
#[allow(unreachable_patterns)]
|
2019-04-09 17:13:28 +00:00
|
|
|
handler => panic!("Unsupported platform: {:?}.", handler),
|
2019-04-02 16:58:39 +00:00
|
|
|
};
|
|
|
|
|
2019-03-22 17:39:15 +00:00
|
|
|
shared_context =
|
2019-04-02 16:58:39 +00:00
|
|
|
unsafe { gst_gl::GLContext::new_wrapped(&gl_display, gl_context, platform, api) }
|
2019-02-27 18:58:47 +00:00
|
|
|
.unwrap();
|
|
|
|
|
2019-03-22 17:39:15 +00:00
|
|
|
shared_context
|
|
|
|
.activate(true)
|
|
|
|
.expect("Couldn't activate wrapped GL context");
|
|
|
|
|
|
|
|
shared_context.fill_info()?;
|
|
|
|
|
|
|
|
let gl_context = shared_context.clone();
|
2019-04-24 13:07:15 +00:00
|
|
|
let events_proxy = events_loop.create_proxy();
|
2019-03-22 17:39:15 +00:00
|
|
|
|
2019-02-28 08:54:32 +00:00
|
|
|
#[allow(clippy::single_match)]
|
2019-02-27 18:58:47 +00:00
|
|
|
bus.set_sync_handler(move |_, msg| {
|
|
|
|
match msg.view() {
|
2019-04-02 16:58:39 +00:00
|
|
|
gst::MessageView::NeedContext(ctxt) => {
|
2019-02-27 18:58:47 +00:00
|
|
|
let context_type = ctxt.get_context_type();
|
|
|
|
if context_type == *gst_gl::GL_DISPLAY_CONTEXT_TYPE {
|
|
|
|
if let Some(el) =
|
|
|
|
msg.get_src().map(|s| s.downcast::<gst::Element>().unwrap())
|
|
|
|
{
|
|
|
|
let context = gst::Context::new(context_type, true);
|
|
|
|
context.set_gl_display(&gl_display);
|
|
|
|
el.set_context(&context);
|
|
|
|
}
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
2019-02-27 18:58:47 +00:00
|
|
|
if context_type == "gst.gl.app_context" {
|
|
|
|
if let Some(el) =
|
|
|
|
msg.get_src().map(|s| s.downcast::<gst::Element>().unwrap())
|
2018-10-18 10:25:14 +00:00
|
|
|
{
|
2019-02-27 18:58:47 +00:00
|
|
|
let mut context = gst::Context::new(context_type, true);
|
|
|
|
{
|
|
|
|
let context = context.get_mut().unwrap();
|
2019-04-24 13:07:15 +00:00
|
|
|
let s = context.get_mut_structure();
|
2020-10-20 13:14:10 +00:00
|
|
|
s.set("context", &gl_context);
|
2019-02-27 18:58:47 +00:00
|
|
|
}
|
|
|
|
el.set_context(&context);
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-27 18:58:47 +00:00
|
|
|
_ => (),
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
let _ = events_proxy.wakeup();
|
|
|
|
|
2019-02-27 18:58:47 +00:00
|
|
|
gst::BusSyncReply::Pass
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
panic!("This example only has Linux support");
|
|
|
|
}
|
2018-10-18 10:25:14 +00:00
|
|
|
|
|
|
|
Ok(App {
|
|
|
|
pipeline,
|
|
|
|
appsink,
|
2019-03-22 17:39:15 +00:00
|
|
|
glupload,
|
2018-10-18 10:25:14 +00:00
|
|
|
bus,
|
2019-07-11 15:50:37 +00:00
|
|
|
events_loop,
|
|
|
|
windowed_context,
|
2019-03-22 17:39:15 +00:00
|
|
|
shared_context,
|
2018-10-18 10:25:14 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
fn setup(
|
|
|
|
&self,
|
|
|
|
events_loop: &glutin::EventsLoop,
|
|
|
|
) -> Result<mpsc::Receiver<gst::Sample>, Error> {
|
|
|
|
let events_proxy = events_loop.create_proxy();
|
2018-10-18 10:25:14 +00:00
|
|
|
let (sender, receiver) = mpsc::channel();
|
|
|
|
self.appsink.set_callbacks(
|
2020-06-25 16:22:25 +00:00
|
|
|
gst_app::AppSinkCallbacks::builder()
|
2018-10-18 10:25:14 +00:00
|
|
|
.new_sample(move |appsink| {
|
2019-12-17 19:00:42 +00:00
|
|
|
let sample = appsink.pull_sample().map_err(|_| gst::FlowError::Eos)?;
|
2018-10-18 10:25:14 +00:00
|
|
|
|
2019-04-23 16:53:10 +00:00
|
|
|
{
|
|
|
|
let _buffer = sample.get_buffer().ok_or_else(|| {
|
2020-12-20 15:09:22 +00:00
|
|
|
element_error!(
|
2019-01-08 16:13:37 +00:00
|
|
|
appsink,
|
|
|
|
gst::ResourceError::Failed,
|
2019-04-23 16:53:10 +00:00
|
|
|
("Failed to get buffer from appsink")
|
2019-01-08 16:13:37 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
gst::FlowError::Error
|
|
|
|
})?;
|
|
|
|
|
2019-04-23 16:53:10 +00:00
|
|
|
let _info = sample
|
|
|
|
.get_caps()
|
2019-12-15 08:36:56 +00:00
|
|
|
.and_then(|caps| gst_video::VideoInfo::from_caps(caps).ok())
|
2019-04-23 16:53:10 +00:00
|
|
|
.ok_or_else(|| {
|
2020-12-20 15:09:22 +00:00
|
|
|
element_error!(
|
2019-04-23 16:53:10 +00:00
|
|
|
appsink,
|
|
|
|
gst::ResourceError::Failed,
|
|
|
|
("Failed to get video info from sample")
|
|
|
|
);
|
|
|
|
|
|
|
|
gst::FlowError::Error
|
|
|
|
})?;
|
|
|
|
}
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
sender
|
2019-01-08 16:13:37 +00:00
|
|
|
.send(sample)
|
|
|
|
.map(|_| gst::FlowSuccess::Ok)
|
2019-04-24 13:07:15 +00:00
|
|
|
.map_err(|_| gst::FlowError::Error)?;
|
|
|
|
|
|
|
|
let _ = events_proxy.wakeup();
|
|
|
|
|
|
|
|
Ok(gst::FlowSuccess::Ok)
|
2018-10-18 10:25:14 +00:00
|
|
|
})
|
|
|
|
.build(),
|
|
|
|
);
|
|
|
|
|
2019-01-08 16:13:37 +00:00
|
|
|
self.pipeline.set_state(gst::State::Playing)?;
|
2018-10-18 10:25:14 +00:00
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
Ok(receiver)
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn map_gl_api(api: glutin::Api) -> gst_gl::GLAPI {
|
|
|
|
match api {
|
|
|
|
glutin::Api::OpenGl => gst_gl::GLAPI::OPENGL3,
|
|
|
|
glutin::Api::OpenGlEs => gst_gl::GLAPI::GLES2,
|
2020-06-30 07:22:52 +00:00
|
|
|
_ => gst_gl::GLAPI::empty(),
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-22 17:39:15 +00:00
|
|
|
fn create_pipeline() -> Result<(gst::Pipeline, gst_app::AppSink, gst::Element), Error> {
|
2018-10-18 10:25:14 +00:00
|
|
|
let pipeline = gst::Pipeline::new(None);
|
|
|
|
let src = gst::ElementFactory::make("videotestsrc", None)
|
2019-12-17 19:00:42 +00:00
|
|
|
.map_err(|_| MissingElement("videotestsrc"))?;
|
|
|
|
let sink = gst::ElementFactory::make("glsinkbin", None)
|
|
|
|
.map_err(|_| MissingElement("glsinkbin"))?;
|
2018-10-18 10:25:14 +00:00
|
|
|
|
|
|
|
pipeline.add_many(&[&src, &sink])?;
|
|
|
|
src.link(&sink)?;
|
|
|
|
|
|
|
|
let appsink = gst::ElementFactory::make("appsink", None)
|
2019-12-17 19:00:42 +00:00
|
|
|
.map_err(|_| MissingElement("appsink"))?
|
2018-10-18 10:25:14 +00:00
|
|
|
.dynamic_cast::<gst_app::AppSink>()
|
|
|
|
.expect("Sink element is expected to be an appsink!");
|
|
|
|
|
|
|
|
sink.set_property("sink", &appsink)?;
|
|
|
|
|
2020-10-20 13:14:10 +00:00
|
|
|
appsink.set_property("enable-last-sample", &false)?;
|
|
|
|
appsink.set_property("emit-signals", &false)?;
|
|
|
|
appsink.set_property("max-buffers", &1u32)?;
|
2018-10-18 10:25:14 +00:00
|
|
|
|
|
|
|
let caps = gst::Caps::builder("video/x-raw")
|
|
|
|
.features(&[&gst_gl::CAPS_FEATURE_MEMORY_GL_MEMORY])
|
2019-10-04 06:11:30 +00:00
|
|
|
.field("format", &gst_video::VideoFormat::Rgba.to_str())
|
2018-10-18 10:25:14 +00:00
|
|
|
.field("texture-target", &"2D")
|
|
|
|
.build();
|
2019-04-15 15:17:42 +00:00
|
|
|
appsink.set_caps(Some(&caps));
|
2018-10-18 10:25:14 +00:00
|
|
|
|
2019-03-22 17:39:15 +00:00
|
|
|
// get the glupload element to extract later the used context in it
|
|
|
|
let mut iter = sink.dynamic_cast::<gst::Bin>().unwrap().iterate_elements();
|
|
|
|
let glupload = loop {
|
|
|
|
match iter.next() {
|
|
|
|
Ok(Some(element)) => {
|
|
|
|
if "glupload" == element.get_factory().unwrap().get_name() {
|
|
|
|
break Some(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(gst::IteratorError::Resync) => iter.resync(),
|
|
|
|
_ => break None,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((pipeline, appsink, glupload.unwrap()))
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
fn handle_messages(bus: &gst::Bus) -> Result<(), Error> {
|
2018-10-18 10:25:14 +00:00
|
|
|
use gst::MessageView;
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
for msg in bus.iter() {
|
2018-10-18 10:25:14 +00:00
|
|
|
match msg.view() {
|
|
|
|
MessageView::Eos(..) => break,
|
|
|
|
MessageView::Error(err) => {
|
2019-10-04 07:47:48 +00:00
|
|
|
return Err(ErrorMessage {
|
2018-10-18 10:25:14 +00:00
|
|
|
src: msg
|
|
|
|
.get_src()
|
2018-12-09 16:09:20 +00:00
|
|
|
.map(|s| String::from(s.get_path_string()))
|
2018-10-18 10:25:14 +00:00
|
|
|
.unwrap_or_else(|| String::from("None")),
|
2020-03-19 11:31:52 +00:00
|
|
|
error: err.get_error().to_string(),
|
2019-12-22 07:59:23 +00:00
|
|
|
debug: err.get_debug(),
|
2020-05-02 23:23:38 +00:00
|
|
|
source: err.get_error(),
|
2019-10-04 07:47:48 +00:00
|
|
|
}
|
|
|
|
.into());
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-04-29 12:43:51 +00:00
|
|
|
|
|
|
|
fn into_context(self: App) -> glutin::WindowedContext<glutin::PossiblyCurrent> {
|
|
|
|
self.windowed_context
|
|
|
|
}
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
2019-04-29 12:43:51 +00:00
|
|
|
fn main_loop(mut app: App) -> Result<glutin::WindowedContext<glutin::PossiblyCurrent>, Error> {
|
2018-10-18 10:25:14 +00:00
|
|
|
println!(
|
|
|
|
"Pixel format of the window's GL context {:?}",
|
2019-03-12 07:27:50 +00:00
|
|
|
app.windowed_context.get_pixel_format()
|
2018-10-18 10:25:14 +00:00
|
|
|
);
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
let gl = load(&app.windowed_context);
|
2018-10-18 10:25:14 +00:00
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
let receiver = app.setup(&app.events_loop)?;
|
2018-10-18 10:25:14 +00:00
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
let mut curr_frame: Option<gst_video::VideoFrame<gst_video::video_frame::Readable>> = None;
|
2018-10-18 10:25:14 +00:00
|
|
|
let mut running = true;
|
2019-03-22 17:39:15 +00:00
|
|
|
let mut gst_gl_context: Option<gst_gl::GLContext> = None;
|
2019-04-24 13:07:15 +00:00
|
|
|
let events_loop = &mut app.events_loop;
|
|
|
|
let windowed_context = &mut app.windowed_context;
|
|
|
|
let bus = &app.bus;
|
2019-03-22 17:39:15 +00:00
|
|
|
|
2018-10-18 10:25:14 +00:00
|
|
|
while running {
|
2019-02-28 08:54:32 +00:00
|
|
|
#[allow(clippy::single_match)]
|
2018-10-18 10:25:14 +00:00
|
|
|
events_loop.poll_events(|event| match event {
|
|
|
|
glutin::Event::WindowEvent { event, .. } => match event {
|
2021-04-10 14:15:38 +00:00
|
|
|
glutin::WindowEvent::CloseRequested
|
|
|
|
| glutin::WindowEvent::KeyboardInput {
|
|
|
|
input:
|
|
|
|
glutin::KeyboardInput {
|
|
|
|
state: glutin::ElementState::Released,
|
|
|
|
virtual_keycode: Some(glutin::VirtualKeyCode::Escape),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
..
|
|
|
|
} => running = false,
|
2018-10-18 10:25:14 +00:00
|
|
|
glutin::WindowEvent::Resized(logical_size) => {
|
2019-04-24 13:07:15 +00:00
|
|
|
let dpi_factor = windowed_context.window().get_hidpi_factor();
|
2019-03-12 07:27:50 +00:00
|
|
|
windowed_context.resize(logical_size.to_physical(dpi_factor));
|
2018-10-18 10:25:14 +00:00
|
|
|
gl.resize(logical_size.to_physical(dpi_factor));
|
|
|
|
}
|
|
|
|
_ => (),
|
|
|
|
},
|
|
|
|
_ => (),
|
|
|
|
});
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
// Handle all pending messages. Whenever there is a message we will
|
|
|
|
// wake up the events loop above
|
|
|
|
App::handle_messages(&bus)?;
|
|
|
|
|
2018-10-18 10:25:14 +00:00
|
|
|
// get the last frame in channel
|
2019-04-24 13:07:15 +00:00
|
|
|
if let Some(sample) = receiver.try_iter().last() {
|
2019-05-23 11:28:09 +00:00
|
|
|
let buffer = sample.get_buffer_owned().unwrap();
|
2018-10-18 10:25:14 +00:00
|
|
|
let info = sample
|
|
|
|
.get_caps()
|
2019-12-15 08:36:56 +00:00
|
|
|
.and_then(|caps| gst_video::VideoInfo::from_caps(caps).ok())
|
2018-10-18 10:25:14 +00:00
|
|
|
.unwrap();
|
2019-03-22 17:39:15 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
if gst_gl_context.is_none() {
|
|
|
|
gst_gl_context = app
|
|
|
|
.glupload
|
|
|
|
.get_property("context")
|
|
|
|
.unwrap()
|
2019-08-13 15:00:17 +00:00
|
|
|
.get::<gst_gl::GLContext>()
|
|
|
|
.unwrap();
|
2019-03-22 17:39:15 +00:00
|
|
|
}
|
|
|
|
|
2019-04-23 16:53:10 +00:00
|
|
|
let sync_meta = buffer.get_meta::<gst_gl::GLSyncMeta>().unwrap();
|
2019-03-22 17:39:15 +00:00
|
|
|
sync_meta.set_sync_point(gst_gl_context.as_ref().unwrap());
|
|
|
|
}
|
|
|
|
|
2019-05-23 11:28:09 +00:00
|
|
|
if let Ok(frame) = gst_video::VideoFrame::from_buffer_readable_gl(buffer, &info) {
|
2019-04-24 13:07:15 +00:00
|
|
|
curr_frame = Some(frame);
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-24 13:07:15 +00:00
|
|
|
if let Some(frame) = curr_frame.as_ref() {
|
2019-03-22 17:39:15 +00:00
|
|
|
let sync_meta = frame.buffer().get_meta::<gst_gl::GLSyncMeta>().unwrap();
|
|
|
|
sync_meta.wait(&app.shared_context);
|
2018-10-18 10:25:14 +00:00
|
|
|
if let Some(texture) = frame.get_texture_id(0) {
|
|
|
|
gl.draw_frame(texture as gl::types::GLuint);
|
|
|
|
}
|
|
|
|
}
|
2019-04-24 13:07:15 +00:00
|
|
|
windowed_context.swap_buffers()?;
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 15:52:51 +00:00
|
|
|
app.pipeline.send_event(gst::event::Eos::new());
|
2019-01-08 16:13:37 +00:00
|
|
|
app.pipeline.set_state(gst::State::Null)?;
|
2018-10-18 10:25:14 +00:00
|
|
|
|
2019-04-29 12:43:51 +00:00
|
|
|
Ok(app.into_context())
|
|
|
|
}
|
|
|
|
|
2021-02-11 16:13:03 +00:00
|
|
|
fn cleanup(_windowed_context: glutin::WindowedContext<glutin::PossiblyCurrent>) {
|
2019-04-29 12:43:51 +00:00
|
|
|
// To ensure that the context stays alive longer than the pipeline or any reference
|
|
|
|
// inside GStreamer to the GL context, its display or anything else. See
|
|
|
|
// https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/issues/196
|
|
|
|
//
|
|
|
|
// We might do any window/GL specific cleanup here as needed.
|
2018-10-18 10:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn example_main() {
|
2021-02-11 16:13:03 +00:00
|
|
|
match App::new().and_then(main_loop).map(cleanup) {
|
2018-10-18 10:25:14 +00:00
|
|
|
Ok(r) => r,
|
|
|
|
Err(e) => eprintln!("Error! {}", e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
examples_common::run(example_main);
|
|
|
|
}
|