examples: Add simple mirror effect implemented as GLFilter element

This commit is contained in:
Marijn Suijten 2021-04-16 20:47:42 +02:00
parent 430d89539e
commit 6c3cc3c422
4 changed files with 220 additions and 26 deletions

View file

@ -161,6 +161,10 @@ required-features = ["ges"]
name = "glwindow"
required-features = ["gl"]
[[bin]]
name = "glfilter"
required-features = ["gl"]
[[bin]]
name = "subclass"

View file

@ -0,0 +1,172 @@
#[path = "../glupload.rs"]
mod glupload;
use glupload::*;
#[path = "../examples-common.rs"]
pub mod examples_common;
/// The fragment shader used for transforming GL textures travelling through the
/// pipeline. This fragment shader links against the default vertex shader
/// provided by [`GLSLStage::new_default_vertex`].
const FRAGMENT_SHADER: &str = r#"
#ifdef GL_ES
precision mediump float;
#endif
// The filter draws a fullscreen quad and provides its coordinates here:
varying vec2 v_texcoord;
// The input texture is bound on a uniform sampler named `tex`:
uniform sampler2D tex;
void main () {
// Flip texture read coordinate on the x axis to create a mirror effect:
gl_FragColor = texture2D(tex, vec2(1.0 - v_texcoord.x, v_texcoord.y));
}
"#;
mod mirror {
use super::{gl, FRAGMENT_SHADER};
use gst::subclass::prelude::*;
use gst_base::subclass::prelude::*;
use gst_base::subclass::BaseTransformMode;
use gst_gl::prelude::*;
use gst_gl::subclass::prelude::*;
use gst_gl::subclass::GLFilterMode;
use gst_gl::*;
use once_cell::sync::Lazy;
use std::sync::Mutex;
pub static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
gst::DebugCategory::new(
"rsglmirrorfilter",
gst::DebugColorFlags::empty(),
Some("Rust GL Mirror Filter"),
)
});
glib::wrapper! {
pub struct GLMirrorFilter(ObjectSubclass<imp::GLMirrorFilter>) @extends gst_gl::GLFilter, gst_gl::GLBaseFilter, gst_base::BaseTransform, gst::Element, gst::Object;
}
impl GLMirrorFilter {
pub fn new(name: Option<&str>) -> Self {
glib::Object::new(&[("name", &name)]).expect("Failed to create GL Mirror Filter Object")
}
}
mod imp {
use super::*;
/// Private data consists of the transformation shader which is compiled
/// in advance to running the actual filter.
#[derive(Default)]
pub struct GLMirrorFilter {
shader: Mutex<Option<GLShader>>,
}
impl GLMirrorFilter {
fn create_shader(
&self,
filter: &<Self as ObjectSubclass>::Type,
context: &GLContext,
) -> Result<(), gst::LoggableError> {
let shader = GLShader::new(context);
let vertex = GLSLStage::new_default_vertex(context);
vertex.compile().unwrap();
shader.attach_unlocked(&vertex)?;
gst::gst_debug!(
CAT,
obj: filter,
"Compiling fragment shader {}",
FRAGMENT_SHADER
);
let fragment = GLSLStage::with_strings(
context,
gl::FRAGMENT_SHADER,
// new_default_vertex is compiled with this version and profile:
GLSLVersion::None,
GLSLProfile::ES | GLSLProfile::COMPATIBILITY,
&[FRAGMENT_SHADER],
);
fragment.compile().unwrap();
shader.attach_unlocked(&fragment)?;
shader.link().unwrap();
gst::gst_debug!(
CAT,
obj: filter,
"Successfully compiled and linked {:?}",
shader
);
*self.shader.lock().unwrap() = Some(shader);
Ok(())
}
}
// See `subclass.rs` for general documentation on creating a subclass. Extended
// information like element metadata have been omitted for brevity.
#[glib::object_subclass]
impl ObjectSubclass for GLMirrorFilter {
const NAME: &'static str = "RsGLMirrorFilter";
type Type = super::GLMirrorFilter;
type ParentType = gst_gl::GLFilter;
}
impl ElementImpl for GLMirrorFilter {}
impl ObjectImpl for GLMirrorFilter {}
impl BaseTransformImpl for GLMirrorFilter {
const MODE: BaseTransformMode = BaseTransformMode::NeverInPlace;
const PASSTHROUGH_ON_SAME_CAPS: bool = false;
const TRANSFORM_IP_ON_PASSTHROUGH: bool = false;
}
impl GLBaseFilterImpl for GLMirrorFilter {
fn gl_start(&self, filter: &Self::Type) -> Result<(), gst::LoggableError> {
// Create a shader when GL is started, knowing that the OpenGL context is
// available.
let context = filter.context().unwrap();
self.create_shader(filter, &context)?;
self.parent_gl_start(filter)
}
}
impl GLFilterImpl for GLMirrorFilter {
const MODE: GLFilterMode = GLFilterMode::Texture;
fn filter_texture(
&self,
filter: &Self::Type,
input: &gst_gl::GLMemory,
output: &gst_gl::GLMemory,
) -> Result<(), gst::LoggableError> {
let shader = self.shader.lock().unwrap();
// Use the underlying filter implementation to transform the input texture into
// an output texture with the shader.
filter.render_to_target_with_shader(
input,
output,
shader
.as_ref()
.expect("No shader, call `create_shader` first!"),
);
self.parent_filter_texture(filter, input, output)
}
}
}
}
fn example_main() {
gst::init().unwrap();
let glfilter = mirror::GLMirrorFilter::new(Some("foo"));
App::new(Some(glfilter.as_ref()))
.and_then(main_loop)
.unwrap_or_else(|e| eprintln!("Error! {}", e))
}
fn main() {
examples_common::run(example_main);
}

View file

@ -6,7 +6,7 @@ use glupload::*;
pub mod examples_common;
fn example_main() {
App::new()
App::new(None)
.and_then(main_loop)
.unwrap_or_else(|e| eprintln!("Error! {}", e))
}

View file

@ -76,7 +76,7 @@ void main() {
#[allow(clippy::unused_unit)]
#[allow(clippy::too_many_arguments)]
#[allow(clippy::manual_non_exhaustive)]
mod gl {
pub(crate) mod gl {
pub use self::Gles2 as Gl;
include!(concat!(env!("OUT_DIR"), "/test_gl_bindings.rs"));
}
@ -327,10 +327,10 @@ pub(crate) struct App {
}
impl App {
pub(crate) fn new() -> Result<App, Error> {
pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result<App, Error> {
gst::init()?;
let (pipeline, appsink, glupload) = App::create_pipeline()?;
let (pipeline, appsink, glupload) = App::create_pipeline(gl_element)?;
let bus = pipeline
.bus()
.expect("Pipeline without bus. Shouldn't happen!");
@ -531,23 +531,18 @@ impl App {
}
}
fn create_pipeline() -> Result<(gst::Pipeline, gst_app::AppSink, gst::Element), Error> {
fn create_pipeline(
gl_element: Option<&gst::Element>,
) -> Result<(gst::Pipeline, gst_app::AppSink, gst::Element), Error> {
let pipeline = gst::Pipeline::new(None);
let src = gst::ElementFactory::make("videotestsrc", None)
.map_err(|_| MissingElement("videotestsrc"))?;
let sink = gst::ElementFactory::make("glsinkbin", None)
.map_err(|_| MissingElement("glsinkbin"))?;
pipeline.add_many(&[&src, &sink])?;
src.link(&sink)?;
let appsink = gst::ElementFactory::make("appsink", None)
.map_err(|_| MissingElement("appsink"))?
.dynamic_cast::<gst_app::AppSink>()
.expect("Sink element is expected to be an appsink!");
sink.set_property("sink", &appsink)?;
appsink.set_property("enable-last-sample", &false)?;
appsink.set_property("emit-signals", &false)?;
appsink.set_property("max-buffers", &1u32)?;
@ -559,6 +554,28 @@ impl App {
.build();
appsink.set_caps(Some(&caps));
if let Some(gl_element) = gl_element {
let glupload = gst::ElementFactory::make("glupload", None)
.map_err(|_| MissingElement("glupload"))?;
pipeline.add_many(&[&src, &glupload])?;
pipeline.add(gl_element)?;
pipeline.add(&appsink)?;
src.link(&glupload)?;
glupload.link(gl_element)?;
gl_element.link(&appsink)?;
Ok((pipeline, appsink, glupload))
} else {
let sink = gst::ElementFactory::make("glsinkbin", None)
.map_err(|_| MissingElement("glsinkbin"))?;
sink.set_property("sink", &appsink)?;
pipeline.add_many(&[&src, &sink])?;
src.link(&sink)?;
// 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 {
@ -575,6 +592,7 @@ impl App {
Ok((pipeline, appsink, glupload.unwrap()))
}
}
fn handle_messages(bus: &gst::Bus) -> Result<(), Error> {
use gst::MessageView;