mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-11-22 17:41:05 +00:00
examples/glupload: Upgrade to glutin 0.31
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>
This commit is contained in:
parent
73180e530b
commit
bf568714b6
4 changed files with 918 additions and 765 deletions
1182
Cargo.lock
generated
1182
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -26,17 +26,20 @@ gst-rtsp-server = { package = "gstreamer-rtsp-server", path = "../gstreamer-rtsp
|
||||||
gst-allocators = { package = "gstreamer-allocators", path = "../gstreamer-allocators", optional = true }
|
gst-allocators = { package = "gstreamer-allocators", path = "../gstreamer-allocators", optional = true }
|
||||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
derive_more = "0.99.5"
|
|
||||||
futures = "0.3"
|
|
||||||
byte-slice-cast = "1"
|
byte-slice-cast = "1"
|
||||||
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", features=["use_glib"], optional = true }
|
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", features=["use_glib"], optional = true }
|
||||||
|
derive_more = "0.99.5"
|
||||||
|
futures = "0.3"
|
||||||
|
glutin = { version = "0.31", optional = true, default-features = false }
|
||||||
|
glutin-winit = { version = "0.4", optional = true, default-features = false }
|
||||||
|
image = { version = "0.24", optional = true, default-features = false, features = ["png", "jpeg"] }
|
||||||
|
memfd = { version = "0.6", optional = true }
|
||||||
|
memmap2 = { version = "0.9", optional = true }
|
||||||
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
||||||
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
||||||
glutin = { version = "0.29", optional = true }
|
raw-window-handle = { version = "0.5", optional = true }
|
||||||
image = { version = "0.24", optional = true, default-features = false, features = ["png", "jpeg"] }
|
|
||||||
memmap2 = { version = "0.9", optional = true }
|
|
||||||
memfd = { version = "0.6", optional = true }
|
|
||||||
uds = { version = "0.4", optional = true }
|
uds = { version = "0.4", optional = true }
|
||||||
|
winit = { version = "0.29", optional = true, default-features = false, features = ["rwh_05"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { version = "0.51", features=["Win32_Graphics_Direct3D11",
|
windows = { version = "0.51", features=["Win32_Graphics_Direct3D11",
|
||||||
|
@ -57,9 +60,9 @@ rtsp-server = ["gst-rtsp-server", "gst-rtsp", "gst-sdp"]
|
||||||
rtsp-server-record = ["gst-rtsp-server", "gst-rtsp", "gio"]
|
rtsp-server-record = ["gst-rtsp-server", "gst-rtsp", "gio"]
|
||||||
pango-cairo = ["pango", "pangocairo", "cairo-rs"]
|
pango-cairo = ["pango", "pangocairo", "cairo-rs"]
|
||||||
overlay-composition = ["pango", "pangocairo", "cairo-rs"]
|
overlay-composition = ["pango", "pangocairo", "cairo-rs"]
|
||||||
gl = ["gst-gl", "gl_generator", "glutin"]
|
gl = ["dep:gst-gl", "dep:gl_generator", "dep:glutin", "dep:glutin-winit", "dep:winit", "dep:raw-window-handle"]
|
||||||
gst-gl-x11 = ["dep:gst-gl-x11"]
|
gst-gl-x11 = ["dep:gst-gl-x11", "glutin-winit?/glx"] # glx turns on x11
|
||||||
gst-gl-egl = ["dep:gst-gl-egl"]
|
gst-gl-egl = ["dep:gst-gl-egl", "glutin-winit?/egl", "glutin-winit?/x11", "glutin-winit?/wayland"] # Use X11 or Wayland via EGL
|
||||||
allocators = ["gst-allocators", "memmap2", "memfd", "uds"]
|
allocators = ["gst-allocators", "memmap2", "memfd", "uds"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -165,7 +165,7 @@ mod mirror {
|
||||||
|
|
||||||
fn example_main() -> Result<()> {
|
fn example_main() -> Result<()> {
|
||||||
gst::init().unwrap();
|
gst::init().unwrap();
|
||||||
let glfilter = mirror::GLMirrorFilter::new(Some("foo"));
|
let glfilter = mirror::GLMirrorFilter::new(Some("Mirror filter"));
|
||||||
App::new(Some(glfilter.as_ref())).and_then(main_loop)
|
App::new(Some(glfilter.as_ref())).and_then(main_loop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,30 @@
|
||||||
// This example demonstrates how to output GL textures, within an
|
//! This example demonstrates how to output GL textures, within an EGL/X11 context provided by the
|
||||||
// EGL/X11 context provided by the application, and render those
|
//! application, and render those textures in the GL application.
|
||||||
// textures in the GL application.
|
//!
|
||||||
|
//! This example follow common patterns from `glutin`:
|
||||||
|
//! <https://github.com/rust-windowing/glutin/blob/master/glutin_examples/src/lib.rs>
|
||||||
|
|
||||||
// {videotestsrc} - { glsinkbin }
|
// {videotestsrc} - { glsinkbin }
|
||||||
|
|
||||||
use std::{ffi::CStr, mem, ptr, sync};
|
use std::{
|
||||||
|
ffi::{CStr, CString},
|
||||||
|
mem,
|
||||||
|
num::NonZeroU32,
|
||||||
|
ptr,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::{Context, Result};
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
|
use glutin::{
|
||||||
|
config::GetGlConfig as _,
|
||||||
|
context::AsRawContext as _,
|
||||||
|
display::{AsRawDisplay as _, GetGlDisplay as _},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use glutin_winit::GlWindow as _;
|
||||||
use gst::element_error;
|
use gst::element_error;
|
||||||
use gst_gl::prelude::*;
|
use gst_gl::prelude::*;
|
||||||
|
use raw_window_handle::HasRawWindowHandle as _;
|
||||||
|
|
||||||
#[derive(Debug, Display, Error)]
|
#[derive(Debug, Display, Error)]
|
||||||
#[display(fmt = "Received error from {src}: {error} (debug: {debug:?})")]
|
#[display(fmt = "Received error from {src}: {error} (debug: {debug:?})")]
|
||||||
|
@ -171,7 +186,7 @@ impl Gl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&self, size: glutin::dpi::PhysicalSize<u32>) {
|
fn resize(&self, size: winit::dpi::PhysicalSize<u32>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.gl
|
self.gl
|
||||||
.Viewport(0, 0, size.width as i32, size.height as i32);
|
.Viewport(0, 0, size.width as i32, size.height as i32);
|
||||||
|
@ -179,14 +194,17 @@ impl Gl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
|
fn load(gl_display: &impl glutin::display::GlDisplay) -> Gl {
|
||||||
let gl = gl::Gl::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _);
|
let gl = gl::Gl::load_with(|symbol| {
|
||||||
|
let symbol = CString::new(symbol).unwrap();
|
||||||
|
gl_display.get_proc_address(&symbol).cast()
|
||||||
|
});
|
||||||
|
|
||||||
let version = unsafe {
|
let version = unsafe {
|
||||||
let data = CStr::from_ptr(gl.GetString(gl::VERSION) as *const _)
|
let version = gl.GetString(gl::VERSION);
|
||||||
.to_bytes()
|
assert!(!version.is_null());
|
||||||
.to_vec();
|
let version = CStr::from_ptr(version.cast());
|
||||||
String::from_utf8(data).unwrap()
|
version.to_string_lossy()
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("OpenGL version {version}");
|
println!("OpenGL version {version}");
|
||||||
|
@ -313,87 +331,148 @@ pub(crate) struct App {
|
||||||
pipeline: gst::Pipeline,
|
pipeline: gst::Pipeline,
|
||||||
appsink: gst_app::AppSink,
|
appsink: gst_app::AppSink,
|
||||||
bus: gst::Bus,
|
bus: gst::Bus,
|
||||||
event_loop: glutin::event_loop::EventLoop<Message>,
|
event_loop: winit::event_loop::EventLoop<Message>,
|
||||||
windowed_context: glutin::WindowedContext<glutin::PossiblyCurrent>,
|
window: Option<winit::window::Window>,
|
||||||
|
not_current_gl_context: Option<glutin::context::NotCurrentContext>,
|
||||||
shared_context: gst_gl::GLContext,
|
shared_context: gst_gl::GLContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result<App, Error> {
|
pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result<App> {
|
||||||
gst::init()?;
|
gst::init()?;
|
||||||
|
|
||||||
let (pipeline, appsink) = App::create_pipeline(gl_element)?;
|
let (pipeline, appsink) = App::create_pipeline(gl_element)?;
|
||||||
let bus = pipeline
|
let bus = pipeline
|
||||||
.bus()
|
.bus()
|
||||||
.expect("Pipeline without bus. Shouldn't happen!");
|
.context("Pipeline without bus. Shouldn't happen!")?;
|
||||||
|
|
||||||
let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build();
|
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build()?;
|
||||||
let window = glutin::window::WindowBuilder::new().with_title("GL rendering");
|
|
||||||
let windowed_context = glutin::ContextBuilder::new()
|
|
||||||
.with_vsync(true)
|
|
||||||
.build_windowed(window, &event_loop)?;
|
|
||||||
|
|
||||||
let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, err)| err)? };
|
// Only Windows requires the window to be present before creating a `glutin::Display`. Other
|
||||||
|
// platforms don't really need one (and on Android, none exists until `Event::Resumed`).
|
||||||
|
let window_builder = cfg!(windows).then(|| {
|
||||||
|
winit::window::WindowBuilder::new()
|
||||||
|
.with_transparent(true)
|
||||||
|
.with_title("GL rendering")
|
||||||
|
});
|
||||||
|
|
||||||
#[cfg(any(feature = "gst-gl-x11"))]
|
let display_builder =
|
||||||
let inner_window = windowed_context.window();
|
glutin_winit::DisplayBuilder::new().with_window_builder(window_builder);
|
||||||
|
// XXX on macOS/cgl only one config can be queried at a time. If transparency is needed,
|
||||||
|
// add .with_transparency(true) to ConfigTemplateBuilder. EGL on X11 doesn't support
|
||||||
|
// transparency at all.
|
||||||
|
let template = glutin::config::ConfigTemplateBuilder::new().with_alpha_size(8);
|
||||||
|
let (window, gl_config) = display_builder
|
||||||
|
.build(&event_loop, template, |configs| {
|
||||||
|
configs
|
||||||
|
.reduce(|current, new_config| {
|
||||||
|
let prefer_transparency =
|
||||||
|
new_config.supports_transparency().unwrap_or(false)
|
||||||
|
& !current.supports_transparency().unwrap_or(false);
|
||||||
|
|
||||||
let shared_context: gst_gl::GLContext;
|
if prefer_transparency || new_config.num_samples() > current.num_samples() {
|
||||||
if cfg!(target_os = "linux") {
|
new_config
|
||||||
#[cfg(any(feature = "gst-gl-x11"))]
|
|
||||||
use glutin::platform::unix::WindowExtUnix;
|
|
||||||
use glutin::platform::{unix::RawHandle, ContextTraitExt};
|
|
||||||
|
|
||||||
let api = App::map_gl_api(windowed_context.get_api());
|
|
||||||
|
|
||||||
let (gl_context, gl_display, platform) = match unsafe { windowed_context.raw_handle() }
|
|
||||||
{
|
|
||||||
#[cfg(any(feature = "gst-gl-egl"))]
|
|
||||||
RawHandle::Egl(egl_context) => {
|
|
||||||
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 {
|
} else {
|
||||||
panic!("EGL window without EGL Display")
|
current
|
||||||
};
|
}
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.expect("Failed to build display");
|
||||||
|
println!(
|
||||||
|
"Picked a config with {} samples and transparency {}. Pixel format: {:?}",
|
||||||
|
gl_config.num_samples(),
|
||||||
|
gl_config.supports_transparency().unwrap_or(false),
|
||||||
|
gl_config.color_buffer_type()
|
||||||
|
);
|
||||||
|
println!("Config supports GL API(s) {:?}", gl_config.api());
|
||||||
|
|
||||||
|
// XXX The display could be obtained from any object created by it, so we can query it from
|
||||||
|
// the config.
|
||||||
|
let gl_display = gl_config.display();
|
||||||
|
let raw_gl_display = gl_display.raw_display();
|
||||||
|
|
||||||
|
println!("Using raw display connection {:?}", raw_gl_display);
|
||||||
|
|
||||||
|
let raw_window_handle = window.as_ref().map(|window| window.raw_window_handle());
|
||||||
|
|
||||||
|
// The context creation part. It can be created before surface and that's how
|
||||||
|
// it's expected in multithreaded + multiwindow operation mode, since you
|
||||||
|
// can send NotCurrentContext, but not Surface.
|
||||||
|
let context_attributes =
|
||||||
|
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
||||||
|
|
||||||
|
// Since glutin by default tries to create OpenGL core context, which may not be
|
||||||
|
// present we should try gles.
|
||||||
|
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||||
|
.with_context_api(glutin::context::ContextApi::Gles(None))
|
||||||
|
.build(raw_window_handle);
|
||||||
|
|
||||||
|
// There are also some old devices that support neither modern OpenGL nor GLES.
|
||||||
|
// To support these we can try and create a 2.1 context.
|
||||||
|
let legacy_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||||
|
.with_context_api(glutin::context::ContextApi::OpenGl(Some(
|
||||||
|
glutin::context::Version::new(2, 1),
|
||||||
|
)))
|
||||||
|
.build(raw_window_handle);
|
||||||
|
|
||||||
|
let not_current_gl_context = unsafe {
|
||||||
|
gl_display
|
||||||
|
.create_context(&gl_config, &context_attributes)
|
||||||
|
.or_else(|_| {
|
||||||
|
gl_display
|
||||||
|
.create_context(&gl_config, &fallback_context_attributes)
|
||||||
|
.or_else(|_| {
|
||||||
|
gl_display.create_context(&gl_config, &legacy_context_attributes)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.context("failed to create context")?;
|
||||||
|
|
||||||
|
let raw_gl_context = not_current_gl_context.raw_context();
|
||||||
|
|
||||||
|
println!("Using raw GL context {:?}", raw_gl_context);
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
compile_error!("This example only has Linux support");
|
||||||
|
|
||||||
|
let api = App::map_gl_api(gl_config.api());
|
||||||
|
|
||||||
|
let (raw_gl_context, gst_gl_display, platform) = match (raw_gl_display, raw_gl_context) {
|
||||||
|
#[cfg(feature = "gst-gl-egl")]
|
||||||
(
|
(
|
||||||
egl_context as usize,
|
glutin::display::RawDisplay::Egl(egl_display),
|
||||||
gl_display.upcast::<gst_gl::GLDisplay>(),
|
glutin::context::RawContext::Egl(egl_context),
|
||||||
gst_gl::GLPlatform::EGL,
|
) => {
|
||||||
)
|
let gl_display =
|
||||||
|
unsafe { gst_gl_egl::GLDisplayEGL::with_egl_display(egl_display as usize) }
|
||||||
|
.context("Failed to create GLDisplayEGL from raw `EGLDisplay`")?
|
||||||
|
.upcast::<gst_gl::GLDisplay>();
|
||||||
|
|
||||||
|
(egl_context as usize, gl_display, gst_gl::GLPlatform::EGL)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "gst-gl-x11")]
|
#[cfg(feature = "gst-gl-x11")]
|
||||||
RawHandle::Glx(glx_context) => {
|
|
||||||
let gl_display = if let Some(display) = inner_window.xlib_display() {
|
|
||||||
unsafe { gst_gl_x11::GLDisplayX11::with_display(display as usize) }.unwrap()
|
|
||||||
} else {
|
|
||||||
panic!("X11 window without X Display")
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
(
|
||||||
glx_context as usize,
|
glutin::display::RawDisplay::Glx(glx_display),
|
||||||
gl_display.upcast::<gst_gl::GLDisplay>(),
|
glutin::context::RawContext::Glx(glx_context),
|
||||||
gst_gl::GLPlatform::GLX,
|
) => {
|
||||||
)
|
let gl_display =
|
||||||
|
unsafe { gst_gl_x11::GLDisplayX11::with_display(glx_display as usize) }
|
||||||
|
.context("Failed to create GLDisplayX11 from raw X11 `Display`")?
|
||||||
|
.upcast::<gst_gl::GLDisplay>();
|
||||||
|
(glx_context as usize, gl_display, gst_gl::GLPlatform::GLX)
|
||||||
}
|
}
|
||||||
#[allow(unreachable_patterns)]
|
#[allow(unreachable_patterns)]
|
||||||
handler => panic!("Unsupported platform: {handler:?}."),
|
handler => anyhow::bail!("Unsupported platform: {handler:?}."),
|
||||||
};
|
};
|
||||||
|
|
||||||
shared_context =
|
let shared_context = unsafe {
|
||||||
unsafe { gst_gl::GLContext::new_wrapped(&gl_display, gl_context, platform, api) }
|
gst_gl::GLContext::new_wrapped(&gst_gl_display, raw_gl_context, platform, api)
|
||||||
.unwrap();
|
}
|
||||||
|
.context("Couldn't wrap GL context")?;
|
||||||
shared_context
|
|
||||||
.activate(true)
|
|
||||||
.expect("Couldn't activate wrapped GL context");
|
|
||||||
|
|
||||||
shared_context.fill_info()?;
|
|
||||||
|
|
||||||
let gl_context = shared_context.clone();
|
let gl_context = shared_context.clone();
|
||||||
let event_proxy = sync::Mutex::new(event_loop.create_proxy());
|
let event_proxy = event_loop.create_proxy();
|
||||||
|
|
||||||
#[allow(clippy::single_match)]
|
#[allow(clippy::single_match)]
|
||||||
bus.set_sync_handler(move |_, msg| {
|
bus.set_sync_handler(move |_, msg| {
|
||||||
|
@ -405,7 +484,7 @@ impl App {
|
||||||
msg.src().map(|s| s.downcast_ref::<gst::Element>().unwrap())
|
msg.src().map(|s| s.downcast_ref::<gst::Element>().unwrap())
|
||||||
{
|
{
|
||||||
let context = gst::Context::new(context_type, true);
|
let context = gst::Context::new(context_type, true);
|
||||||
context.set_gl_display(&gl_display);
|
context.set_gl_display(&gst_gl_display);
|
||||||
el.set_context(&context);
|
el.set_context(&context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -426,27 +505,25 @@ impl App {
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = event_proxy.lock().unwrap().send_event(Message::BusEvent) {
|
if let Err(e) = event_proxy.send_event(Message::BusEvent) {
|
||||||
eprintln!("Failed to send BusEvent to event proxy: {e}")
|
eprintln!("Failed to send BusEvent to event proxy: {e}")
|
||||||
}
|
}
|
||||||
|
|
||||||
gst::BusSyncReply::Pass
|
gst::BusSyncReply::Pass
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
panic!("This example only has Linux support");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(App {
|
Ok(App {
|
||||||
pipeline,
|
pipeline,
|
||||||
appsink,
|
appsink,
|
||||||
bus,
|
bus,
|
||||||
event_loop,
|
event_loop,
|
||||||
windowed_context,
|
window,
|
||||||
|
not_current_gl_context: Some(not_current_gl_context),
|
||||||
shared_context,
|
shared_context,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&self, event_loop: &glutin::event_loop::EventLoop<Message>) -> Result<(), Error> {
|
fn setup(&self, event_loop: &winit::event_loop::EventLoop<Message>) -> Result<()> {
|
||||||
let event_proxy = event_loop.create_proxy();
|
let event_proxy = event_loop.create_proxy();
|
||||||
self.appsink.set_callbacks(
|
self.appsink.set_callbacks(
|
||||||
gst_app::AppSinkCallbacks::builder()
|
gst_app::AppSinkCallbacks::builder()
|
||||||
|
@ -510,22 +587,33 @@ impl App {
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.pipeline.set_state(gst::State::Playing)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_gl_api(api: glutin::Api) -> gst_gl::GLAPI {
|
/// Converts from <https://docs.rs/glutin/latest/glutin/config/struct.Api.html> to
|
||||||
match api {
|
/// <https://gstreamer.freedesktop.org/documentation/gl/gstglapi.html?gi-language=c#GstGLAPI>.
|
||||||
glutin::Api::OpenGl => gst_gl::GLAPI::OPENGL3,
|
fn map_gl_api(api: glutin::config::Api) -> gst_gl::GLAPI {
|
||||||
glutin::Api::OpenGlEs => gst_gl::GLAPI::GLES2,
|
use glutin::config::Api;
|
||||||
_ => gst_gl::GLAPI::empty(),
|
use gst_gl::GLAPI;
|
||||||
}
|
|
||||||
|
let mut gst_gl_api = GLAPI::empty();
|
||||||
|
// In gstreamer:
|
||||||
|
// GLAPI::OPENGL: Desktop OpenGL up to and including 3.1. The compatibility profile when the OpenGL version is >= 3.2
|
||||||
|
// GLAPI::OPENGL3: Desktop OpenGL >= 3.2 core profile
|
||||||
|
// In glutin, API::OPENGL is set for every context API, except EGL where it is set based on
|
||||||
|
// EGL_RENDERABLE_TYPE containing EGL_OPENGL_BIT:
|
||||||
|
// https://registry.khronos.org/EGL/sdk/docs/man/html/eglChooseConfig.xhtml
|
||||||
|
gst_gl_api.set(GLAPI::OPENGL | GLAPI::OPENGL3, api.contains(Api::OPENGL));
|
||||||
|
gst_gl_api.set(GLAPI::GLES1, api.contains(Api::GLES1));
|
||||||
|
// OpenGL ES 2.x and 3.x
|
||||||
|
gst_gl_api.set(GLAPI::GLES2, api.intersects(Api::GLES2 | Api::GLES3));
|
||||||
|
|
||||||
|
gst_gl_api
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_pipeline(
|
fn create_pipeline(
|
||||||
gl_element: Option<&gst::Element>,
|
gl_element: Option<&gst::Element>,
|
||||||
) -> Result<(gst::Pipeline, gst_app::AppSink), Error> {
|
) -> Result<(gst::Pipeline, gst_app::AppSink)> {
|
||||||
let pipeline = gst::Pipeline::default();
|
let pipeline = gst::Pipeline::default();
|
||||||
let src = gst::ElementFactory::make("videotestsrc").build()?;
|
let src = gst::ElementFactory::make("videotestsrc").build()?;
|
||||||
|
|
||||||
|
@ -565,7 +653,7 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_messages(bus: &gst::Bus) -> Result<(), Error> {
|
fn handle_messages(bus: &gst::Bus) -> Result<()> {
|
||||||
use gst::MessageView;
|
use gst::MessageView;
|
||||||
|
|
||||||
for msg in bus.iter() {
|
for msg in bus.iter() {
|
||||||
|
@ -590,69 +678,133 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn main_loop(app: App) -> Result<(), Error> {
|
pub(crate) fn main_loop(app: App) -> Result<()> {
|
||||||
app.setup(&app.event_loop)?;
|
app.setup(&app.event_loop)?;
|
||||||
|
|
||||||
println!(
|
|
||||||
"Pixel format of the window's GL context {:?}",
|
|
||||||
app.windowed_context.get_pixel_format()
|
|
||||||
);
|
|
||||||
|
|
||||||
let gl = load(&app.windowed_context);
|
|
||||||
|
|
||||||
let mut curr_frame: Option<gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>> = None;
|
|
||||||
|
|
||||||
let App {
|
let App {
|
||||||
|
pipeline,
|
||||||
bus,
|
bus,
|
||||||
event_loop,
|
event_loop,
|
||||||
pipeline,
|
mut window,
|
||||||
|
mut not_current_gl_context,
|
||||||
shared_context,
|
shared_context,
|
||||||
windowed_context,
|
|
||||||
..
|
..
|
||||||
} = app;
|
} = app;
|
||||||
|
|
||||||
event_loop.run(move |event, _, cf| {
|
let mut curr_frame: Option<gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>> = None;
|
||||||
*cf = glutin::event_loop::ControlFlow::Wait;
|
|
||||||
|
let mut running_state = None::<(
|
||||||
|
Gl,
|
||||||
|
glutin::context::PossiblyCurrentContext,
|
||||||
|
glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||||
|
)>;
|
||||||
|
|
||||||
|
Ok(event_loop.run(move |event, window_target| {
|
||||||
|
window_target.set_control_flow(winit::event_loop::ControlFlow::Wait);
|
||||||
|
|
||||||
let mut needs_redraw = false;
|
let mut needs_redraw = false;
|
||||||
match event {
|
match event {
|
||||||
glutin::event::Event::LoopDestroyed => {
|
winit::event::Event::LoopExiting => {
|
||||||
pipeline.send_event(gst::event::Eos::new());
|
pipeline.send_event(gst::event::Eos::new());
|
||||||
pipeline.set_state(gst::State::Null).unwrap();
|
pipeline.set_state(gst::State::Null).unwrap();
|
||||||
}
|
}
|
||||||
glutin::event::Event::WindowEvent { event, .. } => match event {
|
winit::event::Event::WindowEvent { event, .. } => match event {
|
||||||
glutin::event::WindowEvent::CloseRequested
|
winit::event::WindowEvent::CloseRequested
|
||||||
| glutin::event::WindowEvent::KeyboardInput {
|
| winit::event::WindowEvent::KeyboardInput {
|
||||||
input:
|
event:
|
||||||
glutin::event::KeyboardInput {
|
winit::event::KeyEvent {
|
||||||
state: glutin::event::ElementState::Released,
|
state: winit::event::ElementState::Released,
|
||||||
virtual_keycode: Some(glutin::event::VirtualKeyCode::Escape),
|
logical_key:
|
||||||
|
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} => *cf = glutin::event_loop::ControlFlow::Exit,
|
} => window_target.exit(),
|
||||||
glutin::event::WindowEvent::Resized(physical_size) => {
|
winit::event::WindowEvent::Resized(size) => {
|
||||||
windowed_context.resize(physical_size);
|
// Some platforms like EGL require resizing GL surface to update the size
|
||||||
gl.resize(physical_size);
|
// Notable platforms here are Wayland and macOS, other don't require it
|
||||||
|
// and the function is no-op, but it's wise to resize it for portability
|
||||||
|
// reasons.
|
||||||
|
if let Some((gl, gl_context, gl_surface)) = &running_state {
|
||||||
|
gl_surface.resize(
|
||||||
|
gl_context,
|
||||||
|
// XXX Ignore minimizing
|
||||||
|
NonZeroU32::new(size.width).unwrap(),
|
||||||
|
NonZeroU32::new(size.height).unwrap(),
|
||||||
|
);
|
||||||
|
gl.resize(size);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
winit::event::WindowEvent::RedrawRequested => needs_redraw = true,
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
glutin::event::Event::RedrawRequested(_) => needs_redraw = true,
|
|
||||||
// Receive a frame
|
// Receive a frame
|
||||||
glutin::event::Event::UserEvent(Message::Frame(info, buffer)) => {
|
winit::event::Event::UserEvent(Message::Frame(info, buffer)) => {
|
||||||
if let Ok(frame) = gst_gl::GLVideoFrame::from_buffer_readable(buffer, &info) {
|
if let Ok(frame) = gst_gl::GLVideoFrame::from_buffer_readable(buffer, &info) {
|
||||||
curr_frame = Some(frame);
|
curr_frame = Some(frame);
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle all pending messages when we are awaken by set_sync_handler
|
// Handle all pending messages when we are awaken by set_sync_handler
|
||||||
glutin::event::Event::UserEvent(Message::BusEvent) => {
|
winit::event::Event::UserEvent(Message::BusEvent) => {
|
||||||
App::handle_messages(&bus).unwrap();
|
App::handle_messages(&bus).unwrap();
|
||||||
}
|
}
|
||||||
|
winit::event::Event::Resumed => {
|
||||||
|
let not_current_gl_context = not_current_gl_context
|
||||||
|
.take()
|
||||||
|
.expect("There must be a NotCurrentContext prior to Event::Resumed");
|
||||||
|
|
||||||
|
let gl_config = not_current_gl_context.config();
|
||||||
|
let gl_display = gl_config.display();
|
||||||
|
|
||||||
|
let window = window.get_or_insert_with(|| {
|
||||||
|
let window_builder = winit::window::WindowBuilder::new().with_transparent(true);
|
||||||
|
glutin_winit::finalize_window(window_target, window_builder, &gl_config)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
let attrs = window.build_surface_attributes(<_>::default());
|
||||||
|
let gl_surface = unsafe {
|
||||||
|
gl_config
|
||||||
|
.display()
|
||||||
|
.create_window_surface(&gl_config, &attrs)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make it current.
|
||||||
|
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
|
||||||
|
|
||||||
|
// Tell GStreamer that the context has been made current (for borrowed contexts,
|
||||||
|
// this does not try to make it current again)
|
||||||
|
shared_context.activate(true).unwrap();
|
||||||
|
|
||||||
|
shared_context
|
||||||
|
.fill_info()
|
||||||
|
.expect("Couldn't fill context info");
|
||||||
|
|
||||||
|
// The context needs to be current for the Renderer to set up shaders and buffers.
|
||||||
|
// It also performs function loading, which needs a current context on WGL.
|
||||||
|
let gl = load(&gl_display);
|
||||||
|
|
||||||
|
// Try setting vsync.
|
||||||
|
if let Err(res) = gl_surface.set_swap_interval(
|
||||||
|
&gl_context,
|
||||||
|
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()),
|
||||||
|
) {
|
||||||
|
eprintln!("Error setting vsync: {res:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.set_state(gst::State::Playing).unwrap();
|
||||||
|
|
||||||
|
assert!(running_state
|
||||||
|
.replace((gl, gl_context, gl_surface))
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if needs_redraw {
|
if needs_redraw {
|
||||||
|
if let Some((gl, gl_context, gl_surface)) = &running_state {
|
||||||
if let Some(frame) = curr_frame.as_ref() {
|
if let Some(frame) = curr_frame.as_ref() {
|
||||||
let sync_meta = frame.buffer().meta::<gst_gl::GLSyncMeta>().unwrap();
|
let sync_meta = frame.buffer().meta::<gst_gl::GLSyncMeta>().unwrap();
|
||||||
sync_meta.wait(&shared_context);
|
sync_meta.wait(&shared_context);
|
||||||
|
@ -660,7 +812,9 @@ pub(crate) fn main_loop(app: App) -> Result<(), Error> {
|
||||||
gl.draw_frame(texture as gl::types::GLuint);
|
gl.draw_frame(texture as gl::types::GLuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
windowed_context.swap_buffers().unwrap();
|
|
||||||
|
gl_surface.swap_buffers(gl_context).unwrap();
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
})?)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue