From 6c3cc3c422e42db0f03892a995b957e40217221e Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Fri, 16 Apr 2021 20:47:42 +0200 Subject: [PATCH] examples: Add simple mirror effect implemented as GLFilter element --- examples/Cargo.toml | 4 + examples/src/bin/glfilter.rs | 172 +++++++++++++++++++++++++++++++++++ examples/src/bin/glwindow.rs | 2 +- examples/src/glupload.rs | 68 +++++++++----- 4 files changed, 220 insertions(+), 26 deletions(-) create mode 100644 examples/src/bin/glfilter.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 426c4d7ac..934b34a23 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -161,6 +161,10 @@ required-features = ["ges"] name = "glwindow" required-features = ["gl"] +[[bin]] +name = "glfilter" +required-features = ["gl"] + [[bin]] name = "subclass" diff --git a/examples/src/bin/glfilter.rs b/examples/src/bin/glfilter.rs new file mode 100644 index 000000000..9676e3033 --- /dev/null +++ b/examples/src/bin/glfilter.rs @@ -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 = Lazy::new(|| { + gst::DebugCategory::new( + "rsglmirrorfilter", + gst::DebugColorFlags::empty(), + Some("Rust GL Mirror Filter"), + ) + }); + + glib::wrapper! { + pub struct GLMirrorFilter(ObjectSubclass) @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>, + } + + impl GLMirrorFilter { + fn create_shader( + &self, + filter: &::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); +} diff --git a/examples/src/bin/glwindow.rs b/examples/src/bin/glwindow.rs index a9467a50f..cb0828998 100644 --- a/examples/src/bin/glwindow.rs +++ b/examples/src/bin/glwindow.rs @@ -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)) } diff --git a/examples/src/glupload.rs b/examples/src/glupload.rs index 5158d0db1..615f75432 100644 --- a/examples/src/glupload.rs +++ b/examples/src/glupload.rs @@ -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 { + pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result { 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::() .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,21 +554,44 @@ impl App { .build(); appsink.set_caps(Some(&caps)); - // get the glupload element to extract later the used context in it - let mut iter = sink.dynamic_cast::().unwrap().iterate_elements(); - let glupload = loop { - match iter.next() { - Ok(Some(element)) => { - if "glupload" == element.factory().unwrap().name() { - break Some(element); - } - } - Err(gst::IteratorError::Resync) => iter.resync(), - _ => break None, - } - }; + if let Some(gl_element) = gl_element { + let glupload = gst::ElementFactory::make("glupload", None) + .map_err(|_| MissingElement("glupload"))?; - Ok((pipeline, appsink, glupload.unwrap())) + 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::().unwrap().iterate_elements(); + let glupload = loop { + match iter.next() { + Ok(Some(element)) => { + if "glupload" == element.factory().unwrap().name() { + break Some(element); + } + } + Err(gst::IteratorError::Resync) => iter.resync(), + _ => break None, + } + }; + + Ok((pipeline, appsink, glupload.unwrap())) + } } fn handle_messages(bus: &gst::Bus) -> Result<(), Error> {