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 compositing_traits::{
13    ExternalImageSource, WebRenderExternalImageApi, WebRenderExternalImageHandlers,
14    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(external_image_handlers: &mut WebRenderExternalImageHandlers) {
146        if !pref!(media_glvideo_enabled) {
147            return;
148        }
149
150        let mut window_gl_context = WINDOW_GL_CONTEXT.lock().unwrap();
151        if window_gl_context.glplayer_thread_sender.is_some() {
152            warn!("Not going to initialize GL accelerated media playback more than once.");
153            return;
154        }
155
156        if matches!(window_gl_context.display, NativeDisplay::Unknown) ||
157            matches!(window_gl_context.context, GlContext::Unknown)
158        {
159            return;
160        }
161
162        let thread_sender = GLPlayerThread::start(external_image_handlers.id_manager());
163        let image_handler = Box::new(GLPlayerExternalImages::new(thread_sender.clone()));
164        external_image_handlers.set_handler(image_handler, WebRenderImageHandlerType::Media);
165        window_gl_context.glplayer_thread_sender = Some(thread_sender);
166    }
167}
168
169impl PlayerGLContext for WindowGLContext {
170    fn get_gl_context(&self) -> GlContext {
171        match self.glplayer_thread_sender {
172            Some(..) => self.context.clone(),
173            None => GlContext::Unknown,
174        }
175    }
176
177    fn get_native_display(&self) -> NativeDisplay {
178        match self.glplayer_thread_sender {
179            Some(..) => self.display.clone(),
180            None => NativeDisplay::Unknown,
181        }
182    }
183
184    fn get_gl_api(&self) -> GlApi {
185        self.api.clone()
186    }
187}
188
189/// Bridge between the webrender::ExternalImage callbacks and the
190/// GLPlayerThreads.
191struct GLPlayerExternalImages {
192    // @FIXME(victor): this should be added when GstGLSyncMeta is
193    // added
194    // webrender_gl: Rc<dyn gl::Gl>,
195    glplayer_channel: IpcSender<GLPlayerMsg>,
196    // Used to avoid creating a new channel on each received WebRender
197    // request.
198    lock_channel: (
199        IpcSender<(u32, Size2D<i32>, usize)>,
200        IpcReceiver<(u32, Size2D<i32>, usize)>,
201    ),
202}
203
204impl GLPlayerExternalImages {
205    fn new(sender: IpcSender<GLPlayerMsg>) -> Self {
206        Self {
207            glplayer_channel: sender,
208            lock_channel: channel().unwrap(),
209        }
210    }
211}
212
213impl WebRenderExternalImageApi for GLPlayerExternalImages {
214    fn lock(&mut self, id: u64) -> (ExternalImageSource<'_>, Size2D<i32>) {
215        // The GLPlayerMsgForward::Lock message inserts a fence in the
216        // GLPlayer command queue.
217        self.glplayer_channel
218            .send(GLPlayerMsg::Lock(id, self.lock_channel.0.clone()))
219            .unwrap();
220        let (image_id, size, _gl_sync) = self.lock_channel.1.recv().unwrap();
221        // The next glWaitSync call is run on the WR thread and it's
222        // used to synchronize the two flows of OpenGL commands in
223        // order to avoid WR using a semi-ready GLPlayer texture.
224        // glWaitSync doesn't block WR thread, it affects only
225        // internal OpenGL subsystem.
226        // self.webrender_gl
227        //    .wait_sync(gl_sync as gl::GLsync, 0, gl::TIMEOUT_IGNORED);
228        (ExternalImageSource::NativeTexture(image_id), size)
229    }
230
231    fn unlock(&mut self, id: u64) {
232        self.glplayer_channel.send(GLPlayerMsg::Unlock(id)).unwrap();
233    }
234}