mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-05-31 22:58:33 +00:00
bf568714b6
Glutin completely detached from `winit` in the `0.30` upgrade, concerning itself exclusively with OpenGL and WSI APIs around it and leaving any windowing system interop to the `raw-window-handle` crate specifically designed for this purpose. This untanglement massively cleans up and simplifies the `glutin` codebase, and expands on surfaceless rendering as well as drawing to simple views (textures) on the screen as is common on Android, without having control over the entire "window" and event loop. Some winit boilerplate is however still provided as part of the `glutin-winit` crate. Most of the `glutin`+`winit` flow in this `glupload` example is adopted from `glutin`'s example, following platform-specific initialization sequences that heavily clutter the code (only creating a window upfront on Windows, only forcing transparency on macOS, and trying various fallback attributes to create a context). At the same time `winit`'s `Event::Resumed` and `Event::Suspended` event strategy is adopted: this event was previously for Android and iOS exclusively - where window handles come and go at the merit of the OS, rather than existing for the lifetime of the application - but is now emitted on all platforms for consistency. A `Surface` (via `RawWindowHandle`) is only available and usable after `Event::Resumed`, where we can create a GL surface and "current" the context on that surface for rendering. This is where the `GstPipeline` will be set to `Playing` so that data starts flowing. The inverse should happen in `Event::Suspended` where the `Surface` has to be given up again after un-currenting, before giving control back to the OS to free the rest of the resources. This will however be implemented when Android is brought online for these examples. Finally, now that the `gst-gl-egl` and `gst-gl-x11` features turn on the relevant features in `glutin` and `winit`, it is now possible to easily test `x11` on Wayland (over XWayland) without even unsetting `WAYLAND_DISPLAY`, by simply compiling the whole stack without EGL/ Wayland support (on the previous example `winit` would always default to a Wayland handle, while `glupload` could only create `GstGLDisplayX11`). Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1336>
175 lines
5.6 KiB
Rust
175 lines
5.6 KiB
Rust
#![allow(clippy::non_send_fields_in_send_ty)]
|
|
|
|
use anyhow::Result;
|
|
|
|
#[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 std::sync::Mutex;
|
|
|
|
use glib::once_cell::sync::Lazy;
|
|
use gst_base::subclass::BaseTransformMode;
|
|
use gst_gl::{
|
|
prelude::*,
|
|
subclass::{prelude::*, GLFilterMode},
|
|
*,
|
|
};
|
|
|
|
use super::{gl, FRAGMENT_SHADER};
|
|
|
|
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::builder().property("name", name).build()
|
|
}
|
|
}
|
|
|
|
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, 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::debug!(
|
|
CAT,
|
|
imp: self,
|
|
"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::debug!(
|
|
CAT,
|
|
imp: self,
|
|
"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 GstObjectImpl 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) -> Result<(), gst::LoggableError> {
|
|
let filter = self.obj();
|
|
|
|
// Create a shader when GL is started, knowing that the OpenGL context is
|
|
// available.
|
|
let context = GLBaseFilterExt::context(&*filter).unwrap();
|
|
self.create_shader(&context)?;
|
|
self.parent_gl_start()
|
|
}
|
|
}
|
|
impl GLFilterImpl for GLMirrorFilter {
|
|
const MODE: GLFilterMode = GLFilterMode::Texture;
|
|
|
|
fn filter_texture(
|
|
&self,
|
|
input: &gst_gl::GLMemory,
|
|
output: &gst_gl::GLMemory,
|
|
) -> Result<(), gst::LoggableError> {
|
|
let filter = self.obj();
|
|
|
|
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(input, output)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn example_main() -> Result<()> {
|
|
gst::init().unwrap();
|
|
let glfilter = mirror::GLMirrorFilter::new(Some("Mirror filter"));
|
|
App::new(Some(glfilter.as_ref())).and_then(main_loop)
|
|
}
|
|
|
|
fn main() -> Result<()> {
|
|
examples_common::run(example_main)
|
|
}
|