gtk4: Add support for GL on Windows

This implements all the workarounds for Windows-specific complications
that the GTK GStreamer mediafile implementation also does.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1255>
This commit is contained in:
Sebastian Dröge 2022-12-23 21:23:42 +02:00 committed by Jayson Reis
parent 2be14b95b3
commit dcb80ac105
4 changed files with 216 additions and 26 deletions

View file

@ -262,6 +262,10 @@ if get_option('gtk4').allowed()
if 'glx' in gl_platforms if 'glx' in gl_platforms
gtk4_features += 'x11glx' gtk4_features += 'x11glx'
endif endif
elif host_system == 'windows'
if 'egl' in gl_platforms
gtk4_features += 'winegl'
endif
endif endif
plugins += {'gtk4': { plugins += {'gtk4': {
'library': 'libgstgtk4', 'library': 'libgstgtk4',

View file

@ -28,6 +28,12 @@ once_cell = "1.0"
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", features = ["v4_6"] } gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", features = ["v4_6"] }
gst_gl = { package = "gstreamer-gl", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] } gst_gl = { package = "gstreamer-gl", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
[target.'cfg(target_os = "windows")'.dependencies]
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", features = ["v4_6"] }
gst_gl = { package = "gstreamer-gl", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
gdk_win32 = { package = "gdk4-win32", git = "https://github.com/gtk-rs/gtk4-rs", features = ["v4_4"]}
windows-sys = { version = "0.42", features = ["Win32_Graphics_OpenGL", "Win32_Foundation", "Win32_Graphics_Gdi"] }
[lib] [lib]
name = "gstgtk4" name = "gstgtk4"
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
@ -42,6 +48,7 @@ static = []
wayland = ["gtk/v4_6", "gdk_wayland", "gst_gl", "gst_gl_wayland"] wayland = ["gtk/v4_6", "gdk_wayland", "gst_gl", "gst_gl_wayland"]
x11glx = ["gtk/v4_6", "gdk_x11", "gst_gl", "gst_gl_x11"] x11glx = ["gtk/v4_6", "gdk_x11", "gst_gl", "gst_gl_x11"]
x11egl = ["gtk/v4_6", "gdk_x11", "gst_gl", "gst_gl_egl"] x11egl = ["gtk/v4_6", "gdk_x11", "gst_gl", "gst_gl_egl"]
winegl = ["gdk_win32/egl", "gst_gl_egl"]
capi = [] capi = []
doc = ["gst/v1_18"] doc = ["gst/v1_18"]

View file

@ -11,7 +11,7 @@
use gst_video::prelude::*; use gst_video::prelude::*;
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
use gst_gl::prelude::*; use gst_gl::prelude::*;
use gtk::{gdk, glib}; use gtk::{gdk, glib};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -20,7 +20,7 @@ use std::collections::{HashMap, HashSet};
pub(crate) struct Frame { pub(crate) struct Frame {
frame: gst_video::VideoFrame<gst_video::video_frame::Readable>, frame: gst_video::VideoFrame<gst_video::video_frame::Readable>,
overlays: Vec<Overlay>, overlays: Vec<Overlay>,
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
wrapped_context: Option<gst_gl::GLContext>, wrapped_context: Option<gst_gl::GLContext>,
} }
@ -94,7 +94,7 @@ fn video_frame_to_memory_texture(
(texture, pixel_aspect_ratio) (texture, pixel_aspect_ratio)
} }
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
fn video_frame_to_gl_texture( fn video_frame_to_gl_texture(
frame: gst_video::VideoFrame<gst_video::video_frame::Readable>, frame: gst_video::VideoFrame<gst_video::video_frame::Readable>,
cached_textures: &mut HashMap<usize, gdk::Texture>, cached_textures: &mut HashMap<usize, gdk::Texture>,
@ -150,11 +150,11 @@ impl Frame {
let width = self.frame.width(); let width = self.frame.width();
let height = self.frame.height(); let height = self.frame.height();
let (texture, pixel_aspect_ratio) = { let (texture, pixel_aspect_ratio) = {
#[cfg(not(any(target_os = "macos", feature = "gst_gl")))] #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst_gl")))]
{ {
video_frame_to_memory_texture(self.frame, cached_textures, &mut used_textures) video_frame_to_memory_texture(self.frame, cached_textures, &mut used_textures)
} }
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
{ {
if let (Some(gdk_ctx), Some(wrapped_ctx)) = if let (Some(gdk_ctx), Some(wrapped_ctx)) =
(gdk_context, self.wrapped_context.as_ref()) (gdk_context, self.wrapped_context.as_ref())
@ -210,11 +210,11 @@ impl Frame {
pub(crate) fn new( pub(crate) fn new(
buffer: &gst::Buffer, buffer: &gst::Buffer,
info: &gst_video::VideoInfo, info: &gst_video::VideoInfo,
#[cfg(any(target_os = "macos", feature = "gst_gl"))] wrapped_context: Option< #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))] wrapped_context: Option<
&gst_gl::GLContext, &gst_gl::GLContext,
>, >,
#[allow(unused_variables)] #[allow(unused_variables)]
#[cfg(not(any(target_os = "macos", feature = "gst_gl")))] #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst_gl")))]
wrapped_context: Option<&()>, wrapped_context: Option<&()>,
) -> Result<Self, gst::FlowError> { ) -> Result<Self, gst::FlowError> {
// Empty buffers get filtered out in show_frame // Empty buffers get filtered out in show_frame
@ -222,7 +222,7 @@ impl Frame {
let mut frame; let mut frame;
#[cfg(not(any(target_os = "macos", feature = "gst_gl")))] #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst_gl")))]
{ {
frame = Self { frame = Self {
frame: gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info) frame: gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info)
@ -230,7 +230,7 @@ impl Frame {
overlays: vec![], overlays: vec![],
}; };
} }
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
{ {
// Check we received a buffer with GL memory and if the context of that memory // Check we received a buffer with GL memory and if the context of that memory
// can share with the wrapped context around the GDK GL context. // can share with the wrapped context around the GDK GL context.

View file

@ -26,15 +26,15 @@ use std::sync::{Mutex, MutexGuard};
use crate::utils; use crate::utils;
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
use gst_gl::prelude::GLContextExt as GstGLContextExt; use gst_gl::prelude::GLContextExt as GstGLContextExt;
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
use gst_gl::prelude::*; use gst_gl::prelude::*;
// Global GL context that is created by the first sink and kept around until the end of the // Global GL context that is created by the first sink and kept around until the end of the
// process. This is provided to other elements in the pipeline to make sure they create GL contexts // process. This is provided to other elements in the pipeline to make sure they create GL contexts
// that are sharing with the GTK GL context. // that are sharing with the GTK GL context.
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
enum GLContext { enum GLContext {
Uninitialized, Uninitialized,
Unsupported, Unsupported,
@ -45,7 +45,7 @@ enum GLContext {
}, },
} }
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
static GL_CONTEXT: Mutex<GLContext> = Mutex::new(GLContext::Uninitialized); static GL_CONTEXT: Mutex<GLContext> = Mutex::new(GLContext::Uninitialized);
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| { static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
@ -154,12 +154,12 @@ impl ElementImpl for PaintableSink {
for features in [ for features in [
None, None,
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
Some(gst::CapsFeatures::new([ Some(gst::CapsFeatures::new([
"memory:GLMemory", "memory:GLMemory",
"meta:GstVideoOverlayComposition", "meta:GstVideoOverlayComposition",
])), ])),
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
Some(gst::CapsFeatures::new(["memory:GLMemory"])), Some(gst::CapsFeatures::new(["memory:GLMemory"])),
Some(gst::CapsFeatures::new([ Some(gst::CapsFeatures::new([
"memory:SystemMemory", "memory:SystemMemory",
@ -225,7 +225,7 @@ impl ElementImpl for PaintableSink {
// Notify the pipeline about the GL display and wrapped context so that any other // Notify the pipeline about the GL display and wrapped context so that any other
// elements in the pipeline ideally use the same / create GL contexts that are // elements in the pipeline ideally use the same / create GL contexts that are
// sharing with this one. // sharing with this one.
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
{ {
let gl_context = GL_CONTEXT.lock().unwrap(); let gl_context = GL_CONTEXT.lock().unwrap();
if let GLContext::Initialized { if let GLContext::Initialized {
@ -331,7 +331,7 @@ impl BaseSinkImpl for PaintableSink {
// TODO: Provide a preferred "window size" here for higher-resolution rendering // TODO: Provide a preferred "window size" here for higher-resolution rendering
query.add_allocation_meta::<gst_video::VideoOverlayCompositionMeta>(None); query.add_allocation_meta::<gst_video::VideoOverlayCompositionMeta>(None);
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
{ {
if let GLContext::Initialized { if let GLContext::Initialized {
wrapped_context, .. wrapped_context, ..
@ -352,7 +352,7 @@ impl BaseSinkImpl for PaintableSink {
gst::log!(CAT, imp: self, "Handling query {:?}", query); gst::log!(CAT, imp: self, "Handling query {:?}", query);
match query.view_mut() { match query.view_mut() {
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
gst::QueryViewMut::Context(q) => { gst::QueryViewMut::Context(q) => {
// Avoid holding the locks while we respond to the query // Avoid holding the locks while we respond to the query
// The objects are ref-counted anyway. // The objects are ref-counted anyway.
@ -408,11 +408,11 @@ impl VideoSinkImpl for PaintableSink {
})?; })?;
let wrapped_context = { let wrapped_context = {
#[cfg(not(any(target_os = "macos", feature = "gst_gl")))] #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst_gl")))]
{ {
None None
} }
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
{ {
let gl_context = GL_CONTEXT.lock().unwrap(); let gl_context = GL_CONTEXT.lock().unwrap();
if let GLContext::Initialized { if let GLContext::Initialized {
@ -474,7 +474,7 @@ impl PaintableSink {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut tmp_caps = Self::pad_templates()[0].caps().clone(); let mut tmp_caps = Self::pad_templates()[0].caps().clone();
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
{ {
// Filter out GL caps from the template pads if we have no context // Filter out GL caps from the template pads if we have no context
if !matches!(&*GL_CONTEXT.lock().unwrap(), GLContext::Initialized { .. }) { if !matches!(&*GL_CONTEXT.lock().unwrap(), GLContext::Initialized { .. }) {
@ -493,7 +493,7 @@ impl PaintableSink {
} }
fn create_paintable(&self, paintable_storage: &mut MutexGuard<Option<ThreadGuard<Paintable>>>) { fn create_paintable(&self, paintable_storage: &mut MutexGuard<Option<ThreadGuard<Paintable>>>) {
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
{ {
self.initialize_gl_context(); self.initialize_gl_context();
} }
@ -523,7 +523,7 @@ impl PaintableSink {
), ),
); );
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
{ {
let gdk_context = if let GLContext::Initialized { gdk_context, .. } = let gdk_context = if let GLContext::Initialized { gdk_context, .. } =
&*GL_CONTEXT.lock().unwrap() &*GL_CONTEXT.lock().unwrap()
@ -534,7 +534,7 @@ impl PaintableSink {
}; };
ThreadGuard::new(Paintable::new(gdk_context)) ThreadGuard::new(Paintable::new(gdk_context))
} }
#[cfg(not(any(target_os = "macos", feature = "gst_gl")))] #[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst_gl")))]
{ {
ThreadGuard::new(Paintable::new(None)) ThreadGuard::new(Paintable::new(None))
} }
@ -545,7 +545,7 @@ impl PaintableSink {
*self.sender.lock().unwrap() = Some(sender); *self.sender.lock().unwrap() = Some(sender);
} }
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
fn initialize_gl_context(&self) { fn initialize_gl_context(&self) {
gst::debug!(CAT, imp: self, "Realizing GDK GL Context"); gst::debug!(CAT, imp: self, "Realizing GDK GL Context");
@ -555,7 +555,7 @@ impl PaintableSink {
}); });
} }
#[cfg(any(target_os = "macos", feature = "gst_gl"))] #[cfg(any(target_os = "macos", target_os = "windows", feature = "gst_gl"))]
fn initialize_gl_context_main(&self) { fn initialize_gl_context_main(&self) {
gst::debug!(CAT, imp: self, "Realizing GDK GL Context from main thread"); gst::debug!(CAT, imp: self, "Realizing GDK GL Context from main thread");
@ -603,6 +603,10 @@ impl PaintableSink {
"GdkWaylandGLContext" => (), "GdkWaylandGLContext" => (),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
"GdkMacosGLContext" => (), "GdkMacosGLContext" => (),
#[cfg(target_os = "windows")]
"GdkWin32GLContext" => (),
#[cfg(all(windows, feature = "winegl"))]
"GdkWin32GLContextEGL" => (),
display => { display => {
gst::error!(CAT, imp: self, "Unsupported GDK display {display} for GL"); gst::error!(CAT, imp: self, "Unsupported GDK display {display} for GL");
return; return;
@ -618,6 +622,7 @@ impl PaintableSink {
gst::info!(CAT, imp: self, "Successfully realized GDK GL Context"); gst::info!(CAT, imp: self, "Successfully realized GDK GL Context");
handle_wgl_makecurrent(&ctx);
gdk_context.make_current(); gdk_context.make_current();
let res = match gdk_context.type_().name() { let res = match gdk_context.type_().name() {
@ -629,6 +634,14 @@ impl PaintableSink {
"GdkWaylandGLContext" => self.initialize_waylandegl(gdk_display), "GdkWaylandGLContext" => self.initialize_waylandegl(gdk_display),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
"GdkMacosGLContext" => self.initialize_macosgl(gdk_display), "GdkMacosGLContext" => self.initialize_macosgl(gdk_display),
#[cfg(target_os = "windows")]
"GdkWin32GLContext" => {
self.initialize_wgl(gdk_display, &gdk_ctx);
}
#[cfg(all(target_os = "windows", feature = "winegl"))]
"GdkWin32GLContextEGL" => {
self.initialize_winegl(gdk_display);
}
display_type => { display_type => {
unreachable!("Unsupported GDK display {display_type} for GL"); unreachable!("Unsupported GDK display {display_type} for GL");
} }
@ -666,6 +679,11 @@ impl PaintableSink {
return; return;
} }
// FIXME: Is this all necessary?
deactivate_gdk_wgl_context(&ctx);
handle_wgl_makecurrent(&ctx);
reactivate_gdk_wgl_context(&ctx);
gst::info!(CAT, imp: self, "Successfully initialized GL Context"); gst::info!(CAT, imp: self, "Successfully initialized GL Context");
*gl_context_guard = GLContext::Initialized { *gl_context_guard = GLContext::Initialized {
@ -864,4 +882,165 @@ impl PaintableSink {
Some((gst_display, wrapped_context)) Some((gst_display, wrapped_context))
} }
} }
#[cfg(target_os = "windows")]
fn initialize_wgl(
&self,
display: gdk::Display,
context: &gdk::GLContext,
) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> {
gst::info!(
CAT,
imp: self,
"Initializing GL with for Windows WGL backend and display."
);
let platform = gst_gl::GLPlatform::WGL;
let gl_api = if context.is_legacy() {
gst_gl::GLAPI::OPENGL
} else {
gst_gl::GLAPI::OPENGL3
};
let gl_ctx = gst_gl::GLContext::current_gl_context(platform);
if gl_ctx == 0 {
gst::error!(CAT, imp: self, "Failed to get handle from GdkGLContext",);
return None;
}
unsafe {
let gst_gl_display = gst_gl::Display::with_type(gst_gl::DisplayType::WIN32);
if gst_gl_display.is_none() {
gst::error!(CAT, imp: self, "Failed to get GL display");
return None;
}
gst_gl_display.filter_gl_api(gl_api);
let wrapped_context =
gst_gl::GLContext::new_wrapped(&gst_gl_display, gl_ctx, platform, gl_api);
let wrapped_context = match wrapped_context {
None => {
gst::error!(CAT, imp: self, "Failed to create wrapped GL context");
return None;
}
Some(wrapped_context) => wrapped_context,
};
Some((gst_gl_display, wrapped_context))
}
}
#[cfg(all(target_os = "windows", feature = "winegl"))]
fn initialize_winegl(
&self,
display: gdk::Display,
) -> Option<(gst_gl::GLDisplay, gst_gl::GLContext)> {
gst::info!(
CAT,
imp: self,
"Initializing GL with for Windows EGL backend and display."
);
let platform = gst_gl::GLPlatform::EGL;
let (gl_api, _, _) = gst_gl::GLContext::current_gl_api(platform);
let gl_ctx = gst_gl::GLContext::current_gl_context(platform);
if gl_ctx == 0 {
gst::error!(CAT, imp: self, "Failed to get handle from GdkGLContext",);
return None;
}
// FIXME: bindings
unsafe {
use gdk_win32::prelude::*;
let d = display.downcast::<gdk_win32::Win32Display>().unwrap();
let egl_display = display.egl_display().unwrap().as_ptr();
let gst_gl_display =
gst_gl_egl::ffi::gst_gl_display_egl_new_with_egl_display(egl_display);
if gst_gl_display.is_null() {
gst::error!(CAT, imp: self, "Failed to get EGL display");
return None;
}
let gst_gl_display: gst_gl::GLDisplay =
from_glib_full(gst_gl_display as *mut gst_gl::ffi::GstGLDisplay);
gst_gl_display.filter_gl_api(gl_api);
let wrapped_context =
gst_gl::GLContext::new_wrapped(&gst_gl_display, gl_ctx, platform, gl_api);
let wrapped_context = match wrapped_context {
None => {
gst::error!(CAT, imp: self, "Failed to create wrapped GL context");
return None;
}
Some(wrapped_context) => wrapped_context,
};
Some((gst_gl_display, wrapped_context))
}
}
} }
// Workaround for Windows specific GL context problems
#[cfg(target_os = "windows")]
fn handle_wgl_makecurrent(ctx: &gdk::GLContext) {
if ctx.type_().name() != "GdkWin32GLContext" {
return;
}
extern "C" {
fn epoxy_handle_external_wglMakeCurrent();
}
unsafe {
epoxy_handle_external_wglMakeCurrent();
}
}
#[cfg(target_os = "windows")]
fn deactivate_gdk_wgl_context(ctx: &gdk::GLContext) {
if ctx.type_().name() != "GdkWin32GLContext" {
return;
}
unsafe {
use gdk_win32::prelude::*;
let surface = context
.surface()
.unwrap()
.downcast::<gdk_win32::Win32Surface>()
.unwrap();
let hwnd = surface.handle();
let hdc = windows_sys::Win32::Graphics::Gdi::GetDC(hwnd);
windows_sys::Win32::Graphics::OpenGL::wglMakeCurrent(hdc, 0);
}
}
#[cfg(target_os = "windows")]
fn reactivate_gdk_wgl_context(ctx: &gdk::GLContext) {
if ctx.type_().name() != "GdkWin32GLContext" {
return;
}
context.make_current();
}
#[cfg(any(target_os = "macos", feature = "gst_gl"))]
#[cfg(not(target_os = "windows"))]
fn handle_wgl_makecurrent(_ctx: &gdk::GLContext) {}
#[cfg(any(target_os = "macos", feature = "gst_gl"))]
#[cfg(not(target_os = "windows"))]
fn deactivate_gdk_wgl_context(_ctx: &gdk::GLContext) {}
#[cfg(any(target_os = "macos", feature = "gst_gl"))]
#[cfg(not(target_os = "windows"))]
fn reactivate_gdk_wgl_context(_ctx: &gdk::GLContext) {}