media/
lib.rs

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