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