aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock96
-rw-r--r--Cargo.toml1
-rw-r--r--etc/taskcluster/decision_task.py9
-rw-r--r--ports/gstplugin/Cargo.toml38
-rw-r--r--ports/gstplugin/README.md131
-rw-r--r--ports/gstplugin/build.rs7
-rw-r--r--ports/gstplugin/lib.rs40
-rw-r--r--ports/gstplugin/logging.rs43
-rw-r--r--ports/gstplugin/resources.rs92
-rw-r--r--ports/gstplugin/servosrc.rs618
-rw-r--r--ports/gstplugin/test.html16
-rw-r--r--python/servo/bootstrap.py2
-rw-r--r--python/servo/command_base.py6
13 files changed, 1088 insertions, 11 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a501e46e194..6da32ec03dc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -581,10 +581,11 @@ dependencies = [
[[package]]
name = "chrono"
-version = "0.4.2"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6"
+checksum = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
dependencies = [
+ "libc",
"num-integer",
"num-traits",
"time",
@@ -1754,6 +1755,21 @@ dependencies = [
]
[[package]]
+name = "git2"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cb400360e8a4d61b10e648285bbfa919bbf9519d0d5d5720354456f44349226"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "openssl-probe",
+ "openssl-sys",
+ "url",
+]
+
+[[package]]
name = "gl_generator"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1916,6 +1932,17 @@ dependencies = [
]
[[package]]
+name = "gst-plugin-version-helper"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90db297b7d445b643b411be6c43e33b78670d0579bec256aeff644b693b0eccb"
+dependencies = [
+ "chrono",
+ "git2",
+ "toml",
+]
+
+[[package]]
name = "gstreamer"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2820,9 +2847,9 @@ dependencies = [
[[package]]
name = "lazy_static"
-version = "1.3.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
@@ -2873,6 +2900,20 @@ dependencies = [
]
[[package]]
+name = "libgit2-sys"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c179ed6d19cd3a051e68c177fbbc214e79ac4724fac3a850ec9f3d3eb8a5578"
+dependencies = [
+ "cc",
+ "libc",
+ "libssh2-sys",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+]
+
+[[package]]
name = "libloading"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2956,6 +2997,20 @@ dependencies = [
]
[[package]]
+name = "libssh2-sys"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fcd5a428a31cbbfe059812d74f4b6cd3b9b7426c2bdaec56993c5365da1c328"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "libz-sys"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3643,6 +3698,12 @@ dependencies = [
]
[[package]]
+name = "openssl-probe"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
+
+[[package]]
name = "openssl-sys"
version = "0.9.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4715,6 +4776,29 @@ dependencies = [
]
[[package]]
+name = "servo-gst-plugin"
+version = "0.0.1"
+dependencies = [
+ "crossbeam-channel",
+ "euclid",
+ "gleam 0.6.18",
+ "glib",
+ "gst-plugin-version-helper",
+ "gstreamer",
+ "gstreamer-base",
+ "gstreamer-gl",
+ "gstreamer-video",
+ "lazy_static",
+ "libservo",
+ "log",
+ "servo-media",
+ "sparkle",
+ "surfman",
+ "surfman-chains",
+ "surfman-chains-api",
+]
+
+[[package]]
name = "servo-media"
version = "0.1.0"
source = "git+https://github.com/servo/media#220ed1388f2ba008b05f5e94aca21dd14aa37290"
@@ -5389,9 +5473,9 @@ dependencies = [
[[package]]
name = "surfman-chains"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43b0d399c15d8f4aad59cd98edbf58e4c96a3b711cf8daf1b006acab8aec97cb"
+checksum = "a2c1b5976b229a807a9e79b3b5248da577948b9882c77f2afce27cf562f80e22"
dependencies = [
"euclid",
"fnv",
diff --git a/Cargo.toml b/Cargo.toml
index 3bb824b3417..96706387e0b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,7 @@
[workspace]
members = [
"ports/glutin",
+ "ports/gstplugin",
"ports/libsimpleservo/capi/",
"ports/libsimpleservo/jniapi/",
"ports/libmlservo/",
diff --git a/etc/taskcluster/decision_task.py b/etc/taskcluster/decision_task.py
index 9342e658830..6abdd0d7e11 100644
--- a/etc/taskcluster/decision_task.py
+++ b/etc/taskcluster/decision_task.py
@@ -184,6 +184,7 @@ def linux_tidy_unit_untrusted():
.with_script("""
./mach test-tidy --no-progress --all
./mach test-tidy --no-progress --self-test
+ ./mach bootstrap-gstreamer
./mach build --dev
./mach test-unit
@@ -207,6 +208,7 @@ def linux_tidy_unit():
./mach build --dev --features canvas2d-raqote
./mach build --dev --features layout-2020
./mach build --dev --libsimpleservo
+ ./mach build --dev -p servo-gst-plugin
./mach test-tidy --no-progress --self-test
./etc/memory_reports_over_time.py --test
@@ -485,6 +487,8 @@ def windows_unit(cached=True):
"mach smoketest --angle",
"mach package --dev",
"mach build --dev --libsimpleservo",
+ "mach build --dev -p servo-gst-plugin",
+
)
.with_artifacts("repo/target/debug/msi/Servo.exe",
"repo/target/debug/msi/Servo.zip")
@@ -814,7 +818,10 @@ def linux_build_task(name, *, build_env=build_env, install_rustc_dev=True):
.with_dockerfile(dockerfile_path("build"))
.with_env(**build_env, **unix_build_env, **linux_build_env)
.with_repo_bundle()
- .with_script("rustup set profile minimal")
+ .with_script("""
+ rustup set profile minimal
+ ./mach bootstrap-gstreamer
+ """)
)
if install_rustc_dev:
# required by components/script_plugins:
diff --git a/ports/gstplugin/Cargo.toml b/ports/gstplugin/Cargo.toml
new file mode 100644
index 00000000000..cb72e6b96f8
--- /dev/null
+++ b/ports/gstplugin/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "servo-gst-plugin"
+description = "A GStreamer plugin that provides servosrc"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+license = "MPL-2.0"
+edition = "2018"
+build = "build.rs"
+repository = "https://github.com/servo/servo/"
+publish = false
+
+[lib]
+name = "gstservoplugin"
+crate-type = ["cdylib"]
+path = "lib.rs"
+
+[dependencies]
+crossbeam-channel = "0.3"
+euclid = "0.20"
+gleam = "0.6"
+glib = { version = "0.8", features = ["subclassing"] }
+gstreamer = { version = "0.14", features = ["subclassing"] }
+gstreamer-base = { version = "0.14", features = ["subclassing"] }
+gstreamer-gl = { version = "0.14", features = ["v1_16"] }
+gstreamer-video = { version = "0.14", features = ["subclassing"] }
+log = "0.4"
+lazy_static = "1.4"
+libservo = {path = "../../components/servo"}
+servo-media = {git = "https://github.com/servo/media"}
+sparkle = "0.1"
+# NOTE: the sm-angle-default feature only enables angle on windows, not other platforms!
+surfman = { version = "0.1", features = ["sm-angle-default", "sm-osmesa"] }
+surfman-chains-api = "0.2"
+surfman-chains = "0.2.1"
+
+[build-dependencies]
+gst-plugin-version-helper = "0.1"
+
diff --git a/ports/gstplugin/README.md b/ports/gstplugin/README.md
new file mode 100644
index 00000000000..6c0a5d5095b
--- /dev/null
+++ b/ports/gstplugin/README.md
@@ -0,0 +1,131 @@
+# A GStreamer plugin which runs servo
+
+## Build
+
+```
+./mach build -r -p servo-gst-plugin
+```
+
+## Install
+
+By default, gstreamer's plugin finder will complain about any libraries it finds that aren't
+gstreamer plugins, so we need to have a directory just for plugins:
+```
+mkdir target/gstplugins
+```
+
+To install:
+```
+cp target/release/libgstservoplugin.* target/gstplugins
+```
+## Run
+
+To run locally:
+```
+GST_PLUGIN_PATH=target/gstplugins \
+ gst-launch-1.0 servosrc \
+ ! queue \
+ ! video/x-raw,framerate=25/1,width=512,height=512 \
+ ! videoflip video-direction=vert \
+ ! autovideosink
+```
+
+To stream over the network:
+```
+GST_PLUGIN_PATH=target/gstplugins \
+ gst-launch-1.0 servosrc \
+ ! queue \
+ ! video/x-raw,framerate=25/1,width=512,height=512 \
+ ! videoconvert \
+ ! videoflip video-direction=vert \
+ ! theoraenc \
+ ! oggmux \
+ ! tcpserversink host=127.0.0.1 port=8080
+```
+
+To save to a file:
+```
+GST_PLUGIN_PATH=target/gstplugins \
+ gst-launch-1.0 servosrc num-buffers=2000 \
+ ! queue \
+ ! video/x-raw,framerate=25/1,width=512,height=512 \
+ ! videoconvert \
+ ! videoflip video-direction=vert \
+ ! theoraenc \
+ ! oggmux \
+ ! filesink location=test.ogg
+```
+
+*Note*: killing the gstreamer pipeline with control-C sometimes locks up macOS to the point
+of needing a power cycle. Killing the pipeline by closing the window seems to work.
+
+## Troubleshooting building the plugin
+
+You may need to make sure rust picks up the right gstreamer, for example:
+```
+PKG_CONFIG_PATH=$PWD/support/linux/gstreamer/gst/lib \
+LD_LIBRARY_PATH=$PWD/support/linux/gstreamer/gst/lib \
+ ./mach build -r -p servo-gst-plugin
+```
+
+## Troubleshooting running the plugin
+
+*Currently x11 support is broken!*
+
+First try:
+```
+GST_PLUGIN_PATH=target/gstplugins \
+ gst-inspect-1.0 servosrc
+```
+
+If that doesn't work, try:
+```
+GST_PLUGIN_PATH=target/gstplugins \
+ gst-in2spect-1.0 target/gstplugins/libgstservoplugin.so
+```
+
+If you get reports about the plugin being blacklisted, remove the (global!) gstreamer cache, e.g. under Linux:
+```
+rm -r ~/.cache/gstreamer-1.0
+```
+
+If you get complaints about not being able to find libraries, set `LD_LIBRARY_PATH`, e.g. to use Servo's Linux gstreamer:
+```
+LD_LIBRARY_PATH=$PWD/support/linux/gstreamer/gst/lib
+```
+
+If you get complaints `cannot allocate memory in static TLS block` this is caused by gstreamer initializing threads using
+the system alloc, which causes problems if those threads run Rust code that uses jemalloc. The fix is to preload the plugin:
+```
+LD_PRELOAD=$PWD/target/gstplugins/libgstservoplugin.so
+```
+
+You may need to set `GST_PLUGIN_SCANNER`, e.g. to use Servo's:
+```
+GST_PLUGIN_SCANNER=$PWD/support/linux/gstreamer/gst/libexec/gstreamer-1.0/gst-plugin-scanner
+```
+
+You may need to include other directories on the plugin search path, e.g. Servo's gstreamer:
+```
+GST_PLUGIN_PATH=$PWD/target/gstplugins/:$PWD/support/linux/gstreamer/gst/lib
+```
+
+Under X11 you may get complaints about X11 threads not being initialized:
+```
+GST_GL_XINITTHREADS=1
+```
+
+Under x11 you may get a frozen display from `autovideosink`, try `ximagesink` instead.
+
+Putting that all together:
+```
+GST_GL_XINITTHREADS=1 \
+GST_PLUGIN_PATH=$PWD/target/gstplugins/:$PWD/support/linux/gstreamer/gst/lib \
+GST_PLUGIN_SCANNER=$PWD/support/linux/gstreamer/gst/libexec/gstreamer-1.0/gst-plugin-scanner \
+LD_LIBRARY_PATH=$PWD/support/linux/gstreamer/gst/lib \
+LD_PRELOAD=$PWD/target/gstplugins/libgstservoplugin.so \
+ gst-launch-1.0 servosrc \
+ ! queue \
+ ! videoflip video-direction=vert \
+ ! ximagesink
+```
diff --git a/ports/gstplugin/build.rs b/ports/gstplugin/build.rs
new file mode 100644
index 00000000000..d1555ca6b37
--- /dev/null
+++ b/ports/gstplugin/build.rs
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+fn main() {
+ gst_plugin_version_helper::get_info()
+}
diff --git a/ports/gstplugin/lib.rs b/ports/gstplugin/lib.rs
new file mode 100644
index 00000000000..af8e384cd1c
--- /dev/null
+++ b/ports/gstplugin/lib.rs
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use glib::subclass::types::ObjectSubclass;
+use gstreamer::gst_plugin_define;
+use servosrc::ServoSrc;
+
+mod logging;
+mod resources;
+mod servosrc;
+
+gst_plugin_define!(
+ servoplugin,
+ env!("CARGO_PKG_DESCRIPTION"),
+ plugin_init,
+ concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
+ "MPL",
+ env!("CARGO_PKG_NAME"),
+ env!("CARGO_PKG_NAME"),
+ env!("CARGO_PKG_REPOSITORY"),
+ env!("BUILD_REL_DATE")
+);
+
+fn plugin_init(plugin: &gstreamer::Plugin) -> Result<(), glib::BoolError> {
+ gstreamer::gst_debug!(logging::CATEGORY, "Initializing logging");
+ log::set_logger(&logging::LOGGER).expect("Failed to set logger");
+ log::set_max_level(log::LevelFilter::Debug);
+
+ log::debug!("Initializing resources");
+ resources::init();
+
+ log::debug!("Registering plugin");
+ gstreamer::Element::register(
+ Some(plugin),
+ "servosrc",
+ gstreamer::Rank::None,
+ ServoSrc::get_type(),
+ )
+}
diff --git a/ports/gstplugin/logging.rs b/ports/gstplugin/logging.rs
new file mode 100644
index 00000000000..f4838ec015d
--- /dev/null
+++ b/ports/gstplugin/logging.rs
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use gstreamer::DebugCategory;
+use gstreamer::DebugColorFlags;
+use gstreamer::DebugLevel;
+use lazy_static::lazy_static;
+
+lazy_static! {
+ pub static ref CATEGORY: DebugCategory =
+ DebugCategory::new("servosrc", DebugColorFlags::empty(), Some("Servo"));
+}
+
+pub static LOGGER: ServoSrcLogger = ServoSrcLogger;
+
+pub struct ServoSrcLogger;
+
+impl log::Log for ServoSrcLogger {
+ fn enabled(&self, _metadata: &log::Metadata) -> bool {
+ true
+ }
+
+ fn log(&self, record: &log::Record) {
+ let lvl = match record.level() {
+ log::Level::Error => DebugLevel::Error,
+ log::Level::Warn => DebugLevel::Warning,
+ log::Level::Info => DebugLevel::Info,
+ log::Level::Debug => DebugLevel::Debug,
+ log::Level::Trace => DebugLevel::Trace,
+ };
+ CATEGORY.log::<gstreamer::Object>(
+ None,
+ lvl,
+ record.file().unwrap_or(""),
+ record.module_path().unwrap_or(""),
+ record.line().unwrap_or(0),
+ record.args().clone(),
+ );
+ }
+
+ fn flush(&self) {}
+}
diff --git a/ports/gstplugin/resources.rs b/ports/gstplugin/resources.rs
new file mode 100644
index 00000000000..eba01e72631
--- /dev/null
+++ b/ports/gstplugin/resources.rs
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// This is a copy of the resource loader from the glutin port
+// TODO: move this to somewhere where it can be shared.
+// https://github.com/servo/servo/issues/24853
+
+use lazy_static::lazy_static;
+use servo::embedder_traits::resources::{self, Resource};
+use std::env;
+use std::fs;
+use std::io;
+use std::path::PathBuf;
+use std::sync::Mutex;
+
+lazy_static! {
+ static ref CMD_RESOURCE_DIR: Mutex<Option<String>> = Mutex::new(None);
+}
+
+struct ResourceReader;
+
+fn filename(file: Resource) -> &'static str {
+ match file {
+ Resource::Preferences => "prefs.json",
+ Resource::BluetoothBlocklist => "gatt_blocklist.txt",
+ Resource::DomainList => "public_domains.txt",
+ Resource::HstsPreloadList => "hsts_preload.json",
+ Resource::SSLCertificates => "certs",
+ Resource::BadCertHTML => "badcert.html",
+ Resource::NetErrorHTML => "neterror.html",
+ Resource::UserAgentCSS => "user-agent.css",
+ Resource::ServoCSS => "servo.css",
+ Resource::PresentationalHintsCSS => "presentational-hints.css",
+ Resource::QuirksModeCSS => "quirks-mode.css",
+ Resource::RippyPNG => "rippy.png",
+ Resource::MediaControlsCSS => "media-controls.css",
+ Resource::MediaControlsJS => "media-controls.js",
+ }
+}
+
+pub fn init() {
+ resources::set(Box::new(ResourceReader));
+}
+
+fn resources_dir_path() -> io::Result<PathBuf> {
+ // This needs to be called before the process is sandboxed
+ // as we only give permission to read inside the resources directory,
+ // not the permissions the "search" for the resources directory.
+ let mut dir = CMD_RESOURCE_DIR.lock().unwrap();
+ if let Some(ref path) = *dir {
+ return Ok(PathBuf::from(path));
+ }
+
+ // FIXME: Find a way to not rely on the executable being
+ // under `<servo source>[/$target_triple]/target/debug`
+ // or `<servo source>[/$target_triple]/target/release`.
+ let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ // Follow symlink
+ path = path.canonicalize()?;
+
+ while path.pop() {
+ path.push("resources");
+ if path.is_dir() {
+ break;
+ }
+ path.pop();
+ // Check for Resources on mac when using a case sensitive filesystem.
+ path.push("Resources");
+ if path.is_dir() {
+ break;
+ }
+ path.pop();
+ }
+ *dir = Some(path.to_str().unwrap().to_owned());
+ Ok(path)
+}
+
+impl resources::ResourceReaderMethods for ResourceReader {
+ fn read(&self, file: Resource) -> Vec<u8> {
+ let file = filename(file);
+ let mut path = resources_dir_path().expect("Can't find resources directory");
+ path.push(file);
+ fs::read(path.clone()).expect(&format!("Can't read file {:?}", path))
+ }
+ fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
+ vec![resources_dir_path().expect("Can't find resources directory")]
+ }
+ fn sandbox_access_files(&self) -> Vec<PathBuf> {
+ vec![]
+ }
+}
diff --git a/ports/gstplugin/servosrc.rs b/ports/gstplugin/servosrc.rs
new file mode 100644
index 00000000000..1698a711031
--- /dev/null
+++ b/ports/gstplugin/servosrc.rs
@@ -0,0 +1,618 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::logging::CATEGORY;
+
+use crossbeam_channel::Receiver;
+use crossbeam_channel::Sender;
+
+use euclid::Point2D;
+use euclid::Rect;
+use euclid::Scale;
+use euclid::Size2D;
+
+use glib::glib_bool_error;
+use glib::glib_object_impl;
+use glib::glib_object_subclass;
+use glib::object::Cast;
+use glib::subclass::object::ObjectImpl;
+use glib::subclass::object::ObjectImplExt;
+use glib::subclass::simple::ClassStruct;
+use glib::subclass::types::ObjectSubclass;
+use gstreamer::gst_element_error;
+use gstreamer::gst_loggable_error;
+use gstreamer::subclass::element::ElementClassSubclassExt;
+use gstreamer::subclass::element::ElementImpl;
+use gstreamer::subclass::ElementInstanceStruct;
+use gstreamer::BufferRef;
+use gstreamer::Caps;
+use gstreamer::CoreError;
+use gstreamer::ErrorMessage;
+use gstreamer::FlowError;
+use gstreamer::FlowSuccess;
+use gstreamer::Format;
+use gstreamer::Fraction;
+use gstreamer::FractionRange;
+use gstreamer::IntRange;
+use gstreamer::LoggableError;
+use gstreamer::PadDirection;
+use gstreamer::PadPresence;
+use gstreamer::PadTemplate;
+use gstreamer_base::subclass::base_src::BaseSrcImpl;
+use gstreamer_base::BaseSrc;
+use gstreamer_base::BaseSrcExt;
+use gstreamer_video::VideoFormat;
+use gstreamer_video::VideoFrameRef;
+use gstreamer_video::VideoInfo;
+
+use log::debug;
+use log::info;
+
+use servo::compositing::windowing::AnimationState;
+use servo::compositing::windowing::EmbedderCoordinates;
+use servo::compositing::windowing::EmbedderMethods;
+use servo::compositing::windowing::WindowEvent;
+use servo::compositing::windowing::WindowMethods;
+use servo::embedder_traits::EventLoopWaker;
+use servo::msg::constellation_msg::TopLevelBrowsingContextId;
+use servo::servo_url::ServoUrl;
+use servo::webrender_api::units::DevicePixel;
+use servo::Servo;
+
+use sparkle::gl;
+use sparkle::gl::types::GLuint;
+use sparkle::gl::Gl;
+
+use surfman::platform::generic::universal::context::Context;
+use surfman::platform::generic::universal::device::Device;
+use surfman::SurfaceAccess;
+use surfman::SurfaceType;
+
+use surfman_chains::SwapChain;
+use surfman_chains_api::SwapChainAPI;
+
+use std::cell::RefCell;
+use std::convert::TryFrom;
+use std::rc::Rc;
+use std::sync::Mutex;
+use std::thread;
+
+pub struct ServoSrc {
+ sender: Sender<ServoSrcMsg>,
+ swap_chain: SwapChain,
+ info: Mutex<Option<VideoInfo>>,
+}
+
+struct ServoSrcGfx {
+ device: Device,
+ context: Context,
+ gl: Rc<Gl>,
+ fbo: GLuint,
+}
+
+impl ServoSrcGfx {
+ fn new() -> ServoSrcGfx {
+ let version = surfman::GLVersion { major: 4, minor: 3 };
+ let flags = surfman::ContextAttributeFlags::empty();
+ let attributes = surfman::ContextAttributes { version, flags };
+
+ let connection = surfman::Connection::new().expect("Failed to create connection");
+ let adapter = surfman::Adapter::default().expect("Failed to create adapter");
+ let mut device =
+ surfman::Device::new(&connection, &adapter).expect("Failed to create device");
+ let descriptor = device
+ .create_context_descriptor(&attributes)
+ .expect("Failed to create descriptor");
+ let context = device
+ .create_context(&descriptor)
+ .expect("Failed to create context");
+ let gl = Gl::gl_fns(gl::ffi_gl::Gl::load_with(|s| {
+ device.get_proc_address(&context, s)
+ }));
+
+ // This is a workaround for surfman having a different bootstrap API with Angle
+ #[cfg(target_os = "windows")]
+ let mut device = device;
+ #[cfg(not(target_os = "windows"))]
+ let mut device = Device::Hardware(device);
+ #[cfg(target_os = "windows")]
+ let mut context = context;
+ #[cfg(not(target_os = "windows"))]
+ let mut context = Context::Hardware(context);
+
+ device.make_context_current(&context).unwrap();
+
+ let size = Size2D::new(512, 512);
+ let surface_type = SurfaceType::Generic { size };
+ let surface = device
+ .create_surface(&mut context, SurfaceAccess::GPUCPU, &surface_type)
+ .expect("Failed to create surface");
+
+ gl.viewport(0, 0, size.width, size.height);
+ debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
+
+ device
+ .bind_surface_to_context(&mut context, surface)
+ .expect("Failed to bind surface");
+ let fbo = device
+ .context_surface_info(&context)
+ .expect("Failed to get context info")
+ .expect("Failed to get context info")
+ .framebuffer_object;
+ gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
+ debug_assert_eq!(
+ (gl.check_framebuffer_status(gl::FRAMEBUFFER), gl.get_error()),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+
+ let fbo = gl.gen_framebuffers(1)[0];
+ debug_assert_eq!(
+ (gl.check_framebuffer_status(gl::FRAMEBUFFER), gl.get_error()),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+
+ device.make_no_context_current().unwrap();
+
+ Self {
+ device,
+ context,
+ gl,
+ fbo,
+ }
+ }
+}
+
+impl Drop for ServoSrcGfx {
+ fn drop(&mut self) {
+ let _ = self.device.destroy_context(&mut self.context);
+ }
+}
+
+thread_local! {
+ static GFX: RefCell<ServoSrcGfx> = RefCell::new(ServoSrcGfx::new());
+}
+
+#[derive(Debug)]
+enum ServoSrcMsg {
+ GetSwapChain(Sender<SwapChain>),
+ Resize(Size2D<i32, DevicePixel>),
+ Heartbeat,
+ Quit,
+}
+
+const DEFAULT_URL: &'static str =
+ "https://rawcdn.githack.com/mrdoob/three.js/r105/examples/webgl_animation_cloth.html";
+
+struct ServoThread {
+ receiver: Receiver<ServoSrcMsg>,
+ swap_chain: SwapChain,
+ servo: Servo<ServoSrcWindow>,
+}
+
+impl ServoThread {
+ fn new(receiver: Receiver<ServoSrcMsg>) -> Self {
+ let embedder = Box::new(ServoSrcEmbedder);
+ let window = Rc::new(ServoSrcWindow::new());
+ let swap_chain = window.swap_chain.clone();
+ let servo = Servo::new(embedder, window);
+ Self {
+ receiver,
+ swap_chain,
+ servo,
+ }
+ }
+
+ fn run(&mut self) {
+ self.new_browser();
+ while let Ok(msg) = self.receiver.recv() {
+ debug!("Servo thread handling message {:?}", msg);
+ match msg {
+ ServoSrcMsg::GetSwapChain(sender) => sender
+ .send(self.swap_chain.clone())
+ .expect("Failed to send swap chain"),
+ ServoSrcMsg::Resize(size) => self.resize(size),
+ ServoSrcMsg::Heartbeat => self.servo.handle_events(vec![]),
+ ServoSrcMsg::Quit => break,
+ }
+ }
+ self.servo.handle_events(vec![WindowEvent::Quit]);
+ }
+
+ fn new_browser(&mut self) {
+ let id = TopLevelBrowsingContextId::new();
+ let url = ServoUrl::parse(DEFAULT_URL).unwrap();
+ self.servo
+ .handle_events(vec![WindowEvent::NewBrowser(url, id)]);
+ }
+
+ fn resize(&mut self, size: Size2D<i32, DevicePixel>) {
+ GFX.with(|gfx| {
+ let mut gfx = gfx.borrow_mut();
+ let gfx = &mut *gfx;
+ self.swap_chain
+ .resize(&mut gfx.device, &mut gfx.context, size.to_untyped())
+ .expect("Failed to resize");
+ gfx.gl.viewport(0, 0, size.width, size.height);
+ let fbo = gfx
+ .device
+ .context_surface_info(&gfx.context)
+ .expect("Failed to get context info")
+ .expect("Failed to get context info")
+ .framebuffer_object;
+ gfx.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+ });
+ self.servo.handle_events(vec![WindowEvent::Resize]);
+ }
+}
+
+impl Drop for ServoThread {
+ fn drop(&mut self) {
+ GFX.with(|gfx| {
+ let mut gfx = gfx.borrow_mut();
+ let gfx = &mut *gfx;
+ self.swap_chain
+ .destroy(&mut gfx.device, &mut gfx.context)
+ .expect("Failed to destroy swap chain")
+ })
+ }
+}
+
+struct ServoSrcEmbedder;
+
+impl EmbedderMethods for ServoSrcEmbedder {
+ fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
+ Box::new(ServoSrcEmbedder)
+ }
+}
+
+impl EventLoopWaker for ServoSrcEmbedder {
+ fn clone_box(&self) -> Box<dyn EventLoopWaker> {
+ Box::new(ServoSrcEmbedder)
+ }
+
+ fn wake(&self) {}
+}
+
+struct ServoSrcWindow {
+ swap_chain: SwapChain,
+ gl: Rc<dyn gleam::gl::Gl>,
+}
+
+impl ServoSrcWindow {
+ fn new() -> Self {
+ GFX.with(|gfx| {
+ let mut gfx = gfx.borrow_mut();
+ let gfx = &mut *gfx;
+ let access = SurfaceAccess::GPUCPU;
+ gfx.device
+ .make_context_current(&mut gfx.context)
+ .expect("Failed to make context current");
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+ let swap_chain = SwapChain::create_attached(&mut gfx.device, &mut gfx.context, access)
+ .expect("Failed to create swap chain");
+ let fbo = gfx
+ .device
+ .context_surface_info(&gfx.context)
+ .expect("Failed to get context info")
+ .expect("Failed to get context info")
+ .framebuffer_object;
+ gfx.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+ let gl = unsafe {
+ gleam::gl::GlFns::load_with(|s| gfx.device.get_proc_address(&gfx.context, s))
+ };
+ Self { swap_chain, gl }
+ })
+ }
+}
+
+impl WindowMethods for ServoSrcWindow {
+ fn present(&self) {
+ GFX.with(|gfx| {
+ debug!("EMBEDDER present");
+ let mut gfx = gfx.borrow_mut();
+ let gfx = &mut *gfx;
+ gfx.device
+ .make_context_current(&mut gfx.context)
+ .expect("Failed to make context current");
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+ let _ = self
+ .swap_chain
+ .swap_buffers(&mut gfx.device, &mut gfx.context);
+ let fbo = gfx
+ .device
+ .context_surface_info(&gfx.context)
+ .expect("Failed to get context info")
+ .expect("Failed to get context info")
+ .framebuffer_object;
+ gfx.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+ let _ = gfx.device.make_no_context_current();
+ })
+ }
+
+ fn make_gl_context_current(&self) {
+ GFX.with(|gfx| {
+ debug!("EMBEDDER make_context_current");
+ let mut gfx = gfx.borrow_mut();
+ let gfx = &mut *gfx;
+ gfx.device
+ .make_context_current(&mut gfx.context)
+ .expect("Failed to make context current");
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+ })
+ }
+
+ fn gl(&self) -> Rc<dyn gleam::gl::Gl> {
+ self.gl.clone()
+ }
+
+ fn get_coordinates(&self) -> EmbedderCoordinates {
+ let size = Size2D::from_untyped(self.swap_chain.size());
+ info!("EMBEDDER coordinates {}", size);
+ let origin = Point2D::origin();
+ EmbedderCoordinates {
+ hidpi_factor: Scale::new(1.0),
+ screen: size,
+ screen_avail: size,
+ window: (size, origin),
+ framebuffer: size,
+ viewport: Rect::new(origin, size),
+ }
+ }
+
+ fn set_animation_state(&self, _: AnimationState) {}
+
+ fn get_gl_context(&self) -> servo_media::player::context::GlContext {
+ servo_media::player::context::GlContext::Unknown
+ }
+
+ fn get_native_display(&self) -> servo_media::player::context::NativeDisplay {
+ servo_media::player::context::NativeDisplay::Unknown
+ }
+
+ fn get_gl_api(&self) -> servo_media::player::context::GlApi {
+ servo_media::player::context::GlApi::OpenGL3
+ }
+}
+
+impl ObjectSubclass for ServoSrc {
+ const NAME: &'static str = "ServoSrc";
+ // gstreamer-gl doesn't have support for GLBaseSrc yet
+ // https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/issues/219
+ type ParentType = BaseSrc;
+ type Instance = ElementInstanceStruct<Self>;
+ type Class = ClassStruct<Self>;
+
+ fn new() -> Self {
+ let (sender, receiver) = crossbeam_channel::bounded(1);
+ thread::spawn(move || ServoThread::new(receiver).run());
+ let (acks, ackr) = crossbeam_channel::bounded(1);
+ let _ = sender.send(ServoSrcMsg::GetSwapChain(acks));
+ let swap_chain = ackr.recv().expect("Failed to get swap chain");
+ let info = Mutex::new(None);
+ Self {
+ sender,
+ swap_chain,
+ info,
+ }
+ }
+
+ fn class_init(klass: &mut ClassStruct<Self>) {
+ klass.set_metadata(
+ "Servo as a gstreamer src",
+ "Filter/Effect/Converter/Video",
+ "The Servo web browser",
+ env!("CARGO_PKG_AUTHORS"),
+ );
+
+ let src_caps = Caps::new_simple(
+ "video/x-raw",
+ &[
+ ("format", &VideoFormat::Bgrx.to_string()),
+ ("width", &IntRange::<i32>::new(1, std::i32::MAX)),
+ ("height", &IntRange::<i32>::new(1, std::i32::MAX)),
+ (
+ "framerate",
+ &FractionRange::new(
+ Fraction::new(1, std::i32::MAX),
+ Fraction::new(std::i32::MAX, 1),
+ ),
+ ),
+ ],
+ );
+ let src_pad_template =
+ PadTemplate::new("src", PadDirection::Src, PadPresence::Always, &src_caps).unwrap();
+ klass.add_pad_template(src_pad_template);
+ }
+
+ glib_object_subclass!();
+}
+
+impl ObjectImpl for ServoSrc {
+ glib_object_impl!();
+
+ fn constructed(&self, obj: &glib::Object) {
+ self.parent_constructed(obj);
+ let basesrc = obj.downcast_ref::<BaseSrc>().unwrap();
+ basesrc.set_live(true);
+ basesrc.set_format(Format::Time);
+ basesrc.set_do_timestamp(true);
+ }
+}
+
+impl ElementImpl for ServoSrc {}
+
+impl BaseSrcImpl for ServoSrc {
+ fn set_caps(&self, _src: &BaseSrc, outcaps: &Caps) -> Result<(), LoggableError> {
+ let info = VideoInfo::from_caps(outcaps)
+ .ok_or_else(|| gst_loggable_error!(CATEGORY, "Failed to get video info"))?;
+ let size = Size2D::new(info.width(), info.height()).to_i32();
+ debug!("Setting servosrc buffer size to {}", size,);
+ self.sender
+ .send(ServoSrcMsg::Resize(size))
+ .map_err(|_| gst_loggable_error!(CATEGORY, "Failed to send video info"))?;
+ *self.info.lock().unwrap() = Some(info);
+ Ok(())
+ }
+
+ fn get_size(&self, _src: &BaseSrc) -> Option<u64> {
+ u64::try_from(self.info.lock().ok()?.as_ref()?.size()).ok()
+ }
+
+ fn start(&self, _src: &BaseSrc) -> Result<(), ErrorMessage> {
+ info!("Starting");
+ Ok(())
+ }
+
+ fn stop(&self, _src: &BaseSrc) -> Result<(), ErrorMessage> {
+ info!("Stopping");
+ let _ = self.sender.send(ServoSrcMsg::Quit);
+ Ok(())
+ }
+
+ fn fill(
+ &self,
+ src: &BaseSrc,
+ _offset: u64,
+ _length: u32,
+ buffer: &mut BufferRef,
+ ) -> Result<FlowSuccess, FlowError> {
+ let guard = self.info.lock().map_err(|_| {
+ gst_element_error!(src, CoreError::Negotiation, ["Lock poisoned"]);
+ FlowError::NotNegotiated
+ })?;
+ let info = guard.as_ref().ok_or_else(|| {
+ gst_element_error!(src, CoreError::Negotiation, ["Caps not set yet"]);
+ FlowError::NotNegotiated
+ })?;
+ let mut frame = VideoFrameRef::from_buffer_ref_writable(buffer, info).ok_or_else(|| {
+ gst_element_error!(
+ src,
+ CoreError::Failed,
+ ["Failed to map output buffer writable"]
+ );
+ FlowError::Error
+ })?;
+ let height = frame.height() as i32;
+ let width = frame.width() as i32;
+ let format = frame.format();
+ debug!(
+ "Filling servosrc buffer {}x{} {:?} {:?}",
+ width, height, format, frame,
+ );
+ let data = frame.plane_data_mut(0).unwrap();
+
+ GFX.with(|gfx| {
+ let mut gfx = gfx.borrow_mut();
+ let gfx = &mut *gfx;
+ if let Some(surface) = self.swap_chain.take_surface() {
+ gfx.device.make_context_current(&gfx.context).unwrap();
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+
+ gfx.gl.viewport(0, 0, width, height);
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+
+ let surface_texture = gfx
+ .device
+ .create_surface_texture(&mut gfx.context, surface)
+ .unwrap();
+ let texture_id = surface_texture.gl_texture();
+
+ gfx.gl.bind_framebuffer(gl::FRAMEBUFFER, gfx.fbo);
+ gfx.gl.framebuffer_texture_2d(
+ gl::FRAMEBUFFER,
+ gl::COLOR_ATTACHMENT0,
+ gfx.device.surface_gl_texture_target(),
+ texture_id,
+ 0,
+ );
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+
+ // TODO: use GL memory to avoid readback
+ gfx.gl.read_pixels_into_buffer(
+ 0,
+ 0,
+ width,
+ height,
+ gl::BGRA,
+ gl::UNSIGNED_BYTE,
+ data,
+ );
+ debug_assert_eq!(
+ (
+ gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
+ gfx.gl.get_error()
+ ),
+ (gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
+ );
+
+ gfx.device.make_no_context_current().unwrap();
+
+ let surface = gfx
+ .device
+ .destroy_surface_texture(&mut gfx.context, surface_texture)
+ .unwrap();
+ self.swap_chain.recycle_surface(surface);
+ }
+ });
+ let _ = self.sender.send(ServoSrcMsg::Heartbeat);
+ Ok(FlowSuccess::Ok)
+ }
+}
diff --git a/ports/gstplugin/test.html b/ports/gstplugin/test.html
new file mode 100644
index 00000000000..a959cd106e8
--- /dev/null
+++ b/ports/gstplugin/test.html
@@ -0,0 +1,16 @@
+<video controls>
+ <source src="http://localhost:8080/" type="video/ogg">
+</video>
+
+<p>Start the video stream with:</p>
+
+<pre>
+ gst-launch-1.0 servosrc \
+ ! queue \
+ ! video/x-raw,framerate=25/1,width=512,height=512 \
+ ! videoconvert \
+ ! videoflip video-direction=vert \
+ ! theoraenc \
+ ! oggmux \
+ ! tcpserversink host=127.0.0.1 port=8080
+</pre>
diff --git a/python/servo/bootstrap.py b/python/servo/bootstrap.py
index f65284073e9..e191798cac5 100644
--- a/python/servo/bootstrap.py
+++ b/python/servo/bootstrap.py
@@ -49,7 +49,7 @@ def install_trusty_deps(force):
def check_gstreamer_lib():
- return subprocess.call(["pkg-config", "--atleast-version=1.14", "gstreamer-1.0"],
+ return subprocess.call(["pkg-config", "--atleast-version=1.16", "gstreamer-1.0"],
stdout=PIPE, stderr=PIPE) == 0
diff --git a/python/servo/command_base.py b/python/servo/command_base.py
index 5a8818333a9..e6b3ec7908b 100644
--- a/python/servo/command_base.py
+++ b/python/servo/command_base.py
@@ -583,10 +583,10 @@ class CommandBase(object):
return True
else:
raise Exception("Your system's gstreamer libraries are out of date \
-(we need at least 1.12). Please run ./mach bootstrap-gstreamer")
+(we need at least 1.16). Please run ./mach bootstrap-gstreamer")
else:
raise Exception("Your system's gstreamer libraries are out of date \
-(we need at least 1.12). If you're unable to \
+(we need at least 1.16). If you're unable to \
install them, let us know by filing a bug!")
return False
@@ -679,7 +679,7 @@ install them, let us know by filing a bug!")
# we append in the reverse order so that system gstreamer libraries
# do not get precedence
extra_path = [libpath] + extra_path
- extra_lib = [libpath] + extra_path
+ extra_lib = [libpath] + extra_lib
append_to_path_env(path.join(libpath, "pkgconfig"), env, "PKG_CONFIG_PATH")
if sys.platform == "linux2":