forked from mirrors/gstreamer-rs
examples: Add simple mirror effect implemented as GLFilter element
This commit is contained in:
parent
430d89539e
commit
6c3cc3c422
4 changed files with 220 additions and 26 deletions
|
@ -161,6 +161,10 @@ required-features = ["ges"]
|
||||||
name = "glwindow"
|
name = "glwindow"
|
||||||
required-features = ["gl"]
|
required-features = ["gl"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "glfilter"
|
||||||
|
required-features = ["gl"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "subclass"
|
name = "subclass"
|
||||||
|
|
||||||
|
|
172
examples/src/bin/glfilter.rs
Normal file
172
examples/src/bin/glfilter.rs
Normal 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);
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ use glupload::*;
|
||||||
pub mod examples_common;
|
pub mod examples_common;
|
||||||
|
|
||||||
fn example_main() {
|
fn example_main() {
|
||||||
App::new()
|
App::new(None)
|
||||||
.and_then(main_loop)
|
.and_then(main_loop)
|
||||||
.unwrap_or_else(|e| eprintln!("Error! {}", e))
|
.unwrap_or_else(|e| eprintln!("Error! {}", e))
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ void main() {
|
||||||
#[allow(clippy::unused_unit)]
|
#[allow(clippy::unused_unit)]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[allow(clippy::manual_non_exhaustive)]
|
#[allow(clippy::manual_non_exhaustive)]
|
||||||
mod gl {
|
pub(crate) mod gl {
|
||||||
pub use self::Gles2 as Gl;
|
pub use self::Gles2 as Gl;
|
||||||
include!(concat!(env!("OUT_DIR"), "/test_gl_bindings.rs"));
|
include!(concat!(env!("OUT_DIR"), "/test_gl_bindings.rs"));
|
||||||
}
|
}
|
||||||
|
@ -327,10 +327,10 @@ pub(crate) struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub(crate) fn new() -> Result<App, Error> {
|
pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result<App, Error> {
|
||||||
gst::init()?;
|
gst::init()?;
|
||||||
|
|
||||||
let (pipeline, appsink, glupload) = App::create_pipeline()?;
|
let (pipeline, appsink, glupload) = App::create_pipeline(gl_element)?;
|
||||||
let bus = pipeline
|
let bus = pipeline
|
||||||
.bus()
|
.bus()
|
||||||
.expect("Pipeline without bus. Shouldn't happen!");
|
.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 pipeline = gst::Pipeline::new(None);
|
||||||
let src = gst::ElementFactory::make("videotestsrc", None)
|
let src = gst::ElementFactory::make("videotestsrc", None)
|
||||||
.map_err(|_| MissingElement("videotestsrc"))?;
|
.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)
|
let appsink = gst::ElementFactory::make("appsink", None)
|
||||||
.map_err(|_| MissingElement("appsink"))?
|
.map_err(|_| MissingElement("appsink"))?
|
||||||
.dynamic_cast::<gst_app::AppSink>()
|
.dynamic_cast::<gst_app::AppSink>()
|
||||||
.expect("Sink element is expected to be an 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("enable-last-sample", &false)?;
|
||||||
appsink.set_property("emit-signals", &false)?;
|
appsink.set_property("emit-signals", &false)?;
|
||||||
appsink.set_property("max-buffers", &1u32)?;
|
appsink.set_property("max-buffers", &1u32)?;
|
||||||
|
@ -559,21 +554,44 @@ impl App {
|
||||||
.build();
|
.build();
|
||||||
appsink.set_caps(Some(&caps));
|
appsink.set_caps(Some(&caps));
|
||||||
|
|
||||||
// get the glupload element to extract later the used context in it
|
if let Some(gl_element) = gl_element {
|
||||||
let mut iter = sink.dynamic_cast::<gst::Bin>().unwrap().iterate_elements();
|
let glupload = gst::ElementFactory::make("glupload", None)
|
||||||
let glupload = loop {
|
.map_err(|_| MissingElement("glupload"))?;
|
||||||
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()))
|
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 {
|
||||||
|
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> {
|
fn handle_messages(bus: &gst::Bus) -> Result<(), Error> {
|
||||||
|
|
Loading…
Reference in a new issue