gtk4: multiple GL sinks in pipeline end up with conflicting GL contexts
In Identity I open multiple videos in different tabs, each of them with its own gtk4paintablesink
in its own playbin3
, but all in a shared Pipeline
. Enabling GL on the sink works fine for one video, but any more videos fail to play because of this error:
0:00:08.077097034 128303 0x562f126844d0 ERROR glconvert gstglcolorconvert.c:2828:_do_convert_one_view:<glcolorconvert2> input memory OpenGL context is different. we have <glcontextegl0> memory has <glcontextegl1>
0:00:08.077188108 128303 0x7fbc70002fb0 WARN glconvertelement gstglcolorconvertelement.c:228:gst_gl_color_convert_element_prepare_output_buffer:<glcolorconvertelement1> error: Failed to convert video buffer
0:00:08.077282046 128303 0x7fbc70002fb0 WARN basetransform gstbasetransform.c:2228:default_generate_output:<glcolorconvertelement1> could not get buffer from pool: error
(identity:128303): Identity-WARNING **: 18:06:55.483: src/player.rs:222: bus: Error from Some(Object { inner: TypedObjectRef { inner: 0x562f134cbd20, type: GstGLColorConvertElement } }): Failed to convert video buffer (Some("../ext/gl/gstglcolorconvertelement.c(228): gst_gl_color_convert_element_prepare_output_buffer (): /GstPipeline:pipeline0/GstPlayBin3:playbin3-1/GstPlaySink:playsink/GstBin:vbin/GstGLSinkBin:glsinkbin1/GstGLColorConvertElement:glcolorconvertelement1"))
It appears that each gtk4paintablesink
instance creates its own GL context, but the pipeline somehow preserves the first GL context it finds (even across separate playbin3
children), messing up the color conversion. (But even if I didn't have separate playbins, I think multiple instances of gtk4paintablesink
in a pipeline should work -- what if you want to split the stream into two parts and output into two paintables?)
Reproducer -- a modified version of gtksink.rs:
use gst::prelude::*;
use gtk::prelude::*;
use gtk::{gdk, gio, glib};
use std::cell::RefCell;
static mut FILE: Option<gio::File> = None;
fn open(app: >k::Application, files: &[gio::File], _: &str) {
unsafe {
FILE = Some(
files
.get(0)
.expect("pass uri as commandline argument")
.clone(),
)
}
app.activate();
}
fn create_ui(app: >k::Application) {
let window = gtk::ApplicationWindow::new(app);
window.set_default_size(640, 480);
let box_ = gtk::Box::new(gtk::Orientation::Horizontal, 0);
box_.set_hexpand(true);
box_.set_homogeneous(true);
window.set_child(Some(&box_));
let pipeline = gst::Pipeline::new(None);
pipeline
.bus()
.unwrap()
.add_watch_local(move |_, msg| {
use gst::MessageView;
match msg.view() {
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
}
_ => (),
};
glib::Continue(true)
})
.unwrap();
pipeline.set_state(gst::State::Paused).unwrap();
// Make two playbins.
for _ in 0..2 {
let sink = gst::ElementFactory::make("gtk4paintablesink")
.build()
.unwrap();
sink.set_state(gst::State::Ready).unwrap();
let paintable = sink.property::<gdk::Paintable>("paintable");
assert!(
paintable
.property::<Option<gdk::GLContext>>("gl-context")
.is_some(),
"we need gl-context for this test"
);
let sink = gst::ElementFactory::make("glsinkbin")
.property("sink", &sink)
.build()
.unwrap();
let playbin = gst::ElementFactory::make("playbin3")
.property("uri", unsafe {
FILE.as_ref()
.expect("pass uri as commandline argument")
.uri()
})
.property("video-sink", sink)
.build()
.unwrap();
let picture = gtk::Picture::new();
picture.set_paintable(Some(&paintable));
box_.append(&picture);
// Pre-roll.
playbin.set_state(gst::State::Paused).unwrap();
// don't feel like dealing with the async weirdness here sorry
std::thread::sleep_ms(1000);
pipeline.add(&playbin).unwrap();
}
window.show();
app.add_window(&window);
pipeline.set_state(gst::State::Playing).unwrap();
let pipeline = RefCell::new(Some(pipeline));
app.connect_shutdown(move |_| {
window.close();
if let Some(pipeline) = pipeline.borrow_mut().take() {
pipeline.set_state(gst::State::Null).unwrap();
pipeline.bus().unwrap().remove_watch().unwrap();
}
});
}
fn main() -> glib::ExitCode {
gst::init().unwrap();
gtk::init().unwrap();
gstgtk4::plugin_register_static().expect("Failed to register gstgtk4 plugin");
let res = {
let app = gtk::Application::new(None::<&str>, gio::ApplicationFlags::HANDLES_OPEN);
app.connect_open(open);
app.connect_activate(create_ui);
app.run()
};
unsafe {
gst::deinit();
}
res
}
$ env GST_DEBUG=1 cargo r --example gtksink --features wayland ~/Videos/Screencasts/Screencast\ from\ 2023-01-19\ 21-04-47.webm
Skipping git submodule `https://github.com/gtk-rs/gir` due to update strategy in .gitmodules
Skipping git submodule `https://github.com/gtk-rs/gir-files` due to update strategy in .gitmodules
Skipping git submodule `https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git` due to update strategy in .gitmodules
Skipping git submodule `https://github.com/gtk-rs/gir` due to update strategy in .gitmodules
Skipping git submodule `https://github.com/gtk-rs/gir-files` due to update strategy in .gitmodules
Skipping git submodule `https://github.com/gtk-rs/gir` due to update strategy in .gitmodules
Skipping git submodule `https://github.com/gtk-rs/gir-files` due to update strategy in .gitmodules
Compiling gst-plugin-gtk4 v0.11.0-alpha.1 (/var/home/yalter/source/rs/gst-plugins-rs/video/gtk4)
warning: use of deprecated function `std::thread::sleep_ms`: replaced by `std::thread::sleep`
--> video/gtk4/examples/gtksink.rs:95:22
|
95 | std::thread::sleep_ms(1000);
| ^^^^^^^^
|
= note: `#[warn(deprecated)]` on by default
warning: `gst-plugin-gtk4` (example "gtksink") generated 1 warning
Finished dev [optimized + debuginfo] target(s) in 6.58s
Running `/var/home/yalter/source/rs/gst-plugins-rs/target/debug/examples/gtksink '/var/home/yalter/Videos/Screencasts/Screencast from 2023-01-19 21-04-47.webm'`
0:00:02.195319567 139682 0x56504810a1e0 ERROR glconvert gstglcolorconvert.c:2717:_do_convert_one_view:<glcolorconvert2> input memory OpenGL context is different. we have <glcontextegl0> memory has <glcontextegl1>
Error from Some("/GstPipeline:pipeline0/GstPlayBin3:playbin3-1/GstPlaySink:playsink/GstBin:vbin/GstGLSinkBin:glsinkbin1/GstGLColorConvertElement:glcolorconvertelement1"): Failed to convert video buffer (Some("../ext/gl/gstglcolorconvertelement.c(228): gst_gl_color_convert_element_prepare_output_buffer (): /GstPipeline:pipeline0/GstPlayBin3:playbin3-1/GstPlaySink:playsink/GstBin:vbin/GstGLSinkBin:glsinkbin1/GstGLColorConvertElement:glcolorconvertelement1"))
Error from Some("/GstPipeline:pipeline0/GstPlayBin3:playbin3-1/GstURIDecodeBin3:uridecodebin3-1/GstDecodebin3:decodebin3-1/GstParseBin:parsebin1/GstMatroskaDemux:matroskademux1"): Internal data stream error. (Some("../gst/matroska/matroska-demux.c(6075): gst_matroska_demux_loop (): /GstPipeline:pipeline0/GstPlayBin3:playbin3-1/GstURIDecodeBin3:uridecodebin3-1/GstDecodebin3:decodebin3-1/GstParseBin:parsebin1/GstMatroskaDemux:matroskademux1:\nstreaming stopped, reason error (-5)"))