media/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
/* 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/. */
#![deny(unsafe_code)]
#![allow(clippy::type_complexity)]
mod media_thread;
use std::sync::{Arc, Mutex};
use euclid::default::Size2D;
use ipc_channel::ipc::{channel, IpcReceiver, IpcSender};
use log::warn;
use serde::{Deserialize, Serialize};
use servo_config::pref;
pub use servo_media::player::context::{GlApi, GlContext, NativeDisplay, PlayerGLContext};
use webrender_traits::{
WebrenderExternalImageApi, WebrenderExternalImageHandlers, WebrenderExternalImageRegistry,
WebrenderImageHandlerType, WebrenderImageSource,
};
use crate::media_thread::GLPlayerThread;
/// A global version of the [`WindowGLContext`] to be shared between the embedder and the
/// constellation. This is only okay to do because OpenGL contexts cannot be used across processes
/// anyway.
///
/// This avoid having to establish a depenency on `media` in `*_traits` crates.
static WINDOW_GL_CONTEXT: Mutex<WindowGLContext> = Mutex::new(WindowGLContext::inactive());
/// These are the messages that the GLPlayer thread will forward to
/// the video player which lives in htmlmediaelement
#[derive(Debug, Deserialize, Serialize)]
pub enum GLPlayerMsgForward {
PlayerId(u64),
Lock(IpcSender<(u32, Size2D<i32>, usize)>),
Unlock(),
}
/// GLPlayer thread Message API
///
/// These are the messages that the thread will receive from the
/// constellation, the webrender::ExternalImageHandle demultiplexor
/// implementation, or a htmlmediaelement
#[derive(Debug, Deserialize, Serialize)]
pub enum GLPlayerMsg {
/// Registers an instantiated player in DOM
RegisterPlayer(IpcSender<GLPlayerMsgForward>),
/// Unregisters a player's ID
UnregisterPlayer(u64),
/// Locks a specific texture from a player. Lock messages are used
/// for a correct synchronization with WebRender external image
/// API.
///
/// WR locks a external texture when it wants to use the shared
/// texture contents.
///
/// The WR client should not change the shared texture content
/// until the Unlock call.
///
/// Currently OpenGL Sync Objects are used to implement the
/// synchronization mechanism.
Lock(u64, IpcSender<(u32, Size2D<i32>, usize)>),
/// Unlocks a specific texture from a player. Unlock messages are
/// used for a correct synchronization with WebRender external
/// image API.
///
/// The WR unlocks a context when it finished reading the shared
/// texture contents.
///
/// Unlock messages are always sent after a Lock message.
Unlock(u64),
/// Frees all resources and closes the thread.
Exit,
}
/// A [`PlayerGLContext`] that renders to a window. Note that if the background
/// thread is not started for this context, then it is inactive (returning
/// `Unknown` values in the trait implementation).
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct WindowGLContext {
/// Application's GL Context
pub context: GlContext,
/// Application's GL Api
pub api: GlApi,
/// Application's native display
pub display: NativeDisplay,
/// A channel to the GLPlayer thread.
pub glplayer_thread_sender: Option<IpcSender<GLPlayerMsg>>,
}
impl WindowGLContext {
/// Create an inactive [`WindowGLContext`].
pub const fn inactive() -> Self {
WindowGLContext {
context: GlContext::Unknown,
api: GlApi::None,
display: NativeDisplay::Unknown,
glplayer_thread_sender: None,
}
}
pub fn register(context: Self) {
*WINDOW_GL_CONTEXT.lock().unwrap() = context;
}
pub fn get() -> Self {
WINDOW_GL_CONTEXT.lock().unwrap().clone()
}
/// Sends an exit message to close the GLPlayerThread.
pub fn exit(&self) {
self.send(GLPlayerMsg::Exit);
}
#[inline]
pub fn send(&self, message: GLPlayerMsg) {
// Don't do anything if GL accelerated playback is disabled.
let Some(sender) = self.glplayer_thread_sender.as_ref() else {
return;
};
if let Err(error) = sender.send(message) {
warn!("Could no longer communicate with GL accelerated media threads: {error}")
}
}
pub fn initialize(display: NativeDisplay, api: GlApi, context: GlContext) {
if matches!(display, NativeDisplay::Unknown) || matches!(context, GlContext::Unknown) {
return;
}
let mut window_gl_context = WINDOW_GL_CONTEXT.lock().unwrap();
if window_gl_context.glplayer_thread_sender.is_some() {
warn!("Not going to initialize GL accelerated media playback more than once.");
return;
}
window_gl_context.context = context;
window_gl_context.display = display;
window_gl_context.api = api;
}
pub fn initialize_image_handler(
external_image_handlers: &mut WebrenderExternalImageHandlers,
external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
) {
if !pref!(media_glvideo_enabled) {
return;
}
let mut window_gl_context = WINDOW_GL_CONTEXT.lock().unwrap();
if window_gl_context.glplayer_thread_sender.is_some() {
warn!("Not going to initialize GL accelerated media playback more than once.");
return;
}
if matches!(window_gl_context.display, NativeDisplay::Unknown) ||
matches!(window_gl_context.context, GlContext::Unknown)
{
return;
}
let thread_sender = GLPlayerThread::start(external_images);
let image_handler = Box::new(GLPlayerExternalImages::new(thread_sender.clone()));
external_image_handlers.set_handler(image_handler, WebrenderImageHandlerType::Media);
window_gl_context.glplayer_thread_sender = Some(thread_sender);
}
}
impl PlayerGLContext for WindowGLContext {
fn get_gl_context(&self) -> GlContext {
match self.glplayer_thread_sender {
Some(..) => self.context.clone(),
None => GlContext::Unknown,
}
}
fn get_native_display(&self) -> NativeDisplay {
match self.glplayer_thread_sender {
Some(..) => self.display.clone(),
None => NativeDisplay::Unknown,
}
}
fn get_gl_api(&self) -> GlApi {
self.api.clone()
}
}
/// Bridge between the webrender::ExternalImage callbacks and the
/// GLPlayerThreads.
struct GLPlayerExternalImages {
// @FIXME(victor): this should be added when GstGLSyncMeta is
// added
//webrender_gl: Rc<dyn gl::Gl>,
glplayer_channel: IpcSender<GLPlayerMsg>,
// Used to avoid creating a new channel on each received WebRender
// request.
lock_channel: (
IpcSender<(u32, Size2D<i32>, usize)>,
IpcReceiver<(u32, Size2D<i32>, usize)>,
),
}
impl GLPlayerExternalImages {
fn new(sender: IpcSender<GLPlayerMsg>) -> Self {
Self {
glplayer_channel: sender,
lock_channel: channel().unwrap(),
}
}
}
impl WebrenderExternalImageApi for GLPlayerExternalImages {
fn lock(&mut self, id: u64) -> (WebrenderImageSource, Size2D<i32>) {
// The GLPlayerMsgForward::Lock message inserts a fence in the
// GLPlayer command queue.
self.glplayer_channel
.send(GLPlayerMsg::Lock(id, self.lock_channel.0.clone()))
.unwrap();
let (image_id, size, _gl_sync) = self.lock_channel.1.recv().unwrap();
// The next glWaitSync call is run on the WR thread and it's
// used to synchronize the two flows of OpenGL commands in
// order to avoid WR using a semi-ready GLPlayer texture.
// glWaitSync doesn't block WR thread, it affects only
// internal OpenGL subsystem.
//self.webrender_gl
// .wait_sync(gl_sync as gl::GLsync, 0, gl::TIMEOUT_IGNORED);
(WebrenderImageSource::TextureHandle(image_id), size)
}
fn unlock(&mut self, id: u64) {
self.glplayer_channel.send(GLPlayerMsg::Unlock(id)).unwrap();
}
}