libmpv2/mpv/
render.rs

1use crate::{Error, Result, mpv::mpv_err};
2use std::collections::HashMap;
3use std::ffi::{CStr, c_char, c_void};
4use std::os::raw::c_int;
5use std::ptr;
6
7type DeleterFn = unsafe fn(*mut c_void);
8
9pub struct RenderContext {
10    ctx: *mut libmpv2_sys::mpv_render_context,
11    update_callback_cleanup: Option<Box<dyn FnOnce()>>,
12}
13
14/// For initializing the mpv OpenGL state via RenderParam::OpenGLInitParams
15pub struct OpenGLInitParams<GLContext> {
16    /// This retrieves OpenGL function pointers, and will use them in subsequent
17    /// operation.
18    /// Usually, you can simply call the GL context APIs from this callback (e.g.
19    /// glXGetProcAddressARB or wglGetProcAddress), but some APIs do not always
20    /// return pointers for all standard functions (even if present); in this
21    /// case you have to compensate by looking up these functions yourself when
22    /// libmpv wants to resolve them through this callback.
23    /// libmpv will not normally attempt to resolve GL functions on its own, nor
24    /// does it link to GL libraries directly.
25    pub get_proc_address: fn(ctx: &GLContext, name: &str) -> *mut c_void,
26
27    /// Value passed as ctx parameter to get_proc_address().
28    pub ctx: GLContext,
29}
30
31/// For RenderParam::FBO
32pub struct FBO {
33    pub fbo: i32,
34    pub width: i32,
35    pub height: i32,
36}
37
38#[repr(u32)]
39#[derive(Clone)]
40pub enum RenderFrameInfoFlag {
41    Present = libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_PRESENT,
42    Redraw = libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_REDRAW,
43    Repeat = libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_REPEAT,
44    BlockVSync = libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_BLOCK_VSYNC,
45}
46
47impl From<u64> for RenderFrameInfoFlag {
48    // mpv_render_frame_info_flag is u32, but mpv_render_frame_info.flags is u64 o\
49    fn from(val: u64) -> Self {
50        let val = val as u32;
51        match val {
52            libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_PRESENT => {
53                RenderFrameInfoFlag::Present
54            }
55            libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_REDRAW => {
56                RenderFrameInfoFlag::Redraw
57            }
58            libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_REPEAT => {
59                RenderFrameInfoFlag::Repeat
60            }
61            libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_BLOCK_VSYNC => {
62                RenderFrameInfoFlag::BlockVSync
63            }
64            _ => panic!("Tried converting invalid value to RenderFrameInfoFlag"),
65        }
66    }
67}
68
69#[derive(Clone)]
70pub struct RenderFrameInfo {
71    pub flags: RenderFrameInfoFlag,
72    pub target_time: i64,
73}
74
75pub use libmpv2_sys::mpv_render_update_flag as MpvRenderUpdate;
76pub mod mpv_render_update {
77    pub use libmpv2_sys::mpv_render_update_flag_MPV_RENDER_UPDATE_FRAME as Frame;
78}
79
80pub enum RenderParamApiType {
81    OpenGl,
82}
83
84pub enum RenderParam<GLContext> {
85    Invalid,
86    ApiType(RenderParamApiType),
87    InitParams(OpenGLInitParams<GLContext>),
88    FBO(FBO),
89    FlipY(bool),
90    Depth(i32),
91    ICCProfile(Vec<u8>),
92    AmbientLight(i32),
93    X11Display(*const c_void),
94    WaylandDisplay(*const c_void),
95    AdvancedControl(bool),
96    NextFrameInfo(RenderFrameInfo),
97    BlockForTargetTime(bool),
98    SkipRendering(bool),
99}
100
101impl<C> From<&RenderParam<C>> for u32 {
102    fn from(val: &RenderParam<C>) -> Self {
103        match val {
104            RenderParam::Invalid => 0,
105            RenderParam::ApiType(_) => 1,
106            RenderParam::InitParams(_) => 2,
107            RenderParam::FBO(_) => 3,
108            RenderParam::FlipY(_) => 4,
109            RenderParam::Depth(_) => 5,
110            RenderParam::ICCProfile(_) => 6,
111            RenderParam::AmbientLight(_) => 7,
112            RenderParam::X11Display(_) => 8,
113            RenderParam::WaylandDisplay(_) => 9,
114            RenderParam::AdvancedControl(_) => 10,
115            RenderParam::NextFrameInfo(_) => 11,
116            RenderParam::BlockForTargetTime(_) => 12,
117            RenderParam::SkipRendering(_) => 13,
118        }
119    }
120}
121
122unsafe extern "C" fn gpa_wrapper<GLContext>(ctx: *mut c_void, name: *const c_char) -> *mut c_void {
123    if ctx.is_null() {
124        panic!("ctx for get_proc_address wrapper is NULL");
125    }
126
127    let params: *mut OpenGLInitParams<GLContext> = ctx as _;
128    let params = unsafe { &*params };
129    (params.get_proc_address)(
130        &params.ctx,
131        unsafe { CStr::from_ptr(name) }
132            .to_str()
133            .expect("Could not convert function name to str"),
134    )
135}
136
137unsafe extern "C" fn ru_wrapper<F: Fn() + Send + 'static>(ctx: *mut c_void) {
138    if ctx.is_null() {
139        panic!("ctx for render_update wrapper is NULL");
140    }
141
142    unsafe { (*(ctx as *mut F))() };
143}
144
145impl<C> From<OpenGLInitParams<C>> for libmpv2_sys::mpv_opengl_init_params {
146    fn from(val: OpenGLInitParams<C>) -> Self {
147        Self {
148            get_proc_address: Some(gpa_wrapper::<OpenGLInitParams<C>>),
149            get_proc_address_ctx: Box::into_raw(Box::new(val)) as *mut c_void,
150        }
151    }
152}
153
154impl<C> From<RenderParam<C>> for libmpv2_sys::mpv_render_param {
155    fn from(val: RenderParam<C>) -> Self {
156        let type_ = u32::from(&val);
157        let data = match val {
158            RenderParam::Invalid => ptr::null_mut(),
159            RenderParam::ApiType(api_type) => match api_type {
160                RenderParamApiType::OpenGl => {
161                    libmpv2_sys::MPV_RENDER_API_TYPE_OPENGL.as_ptr() as *mut c_void
162                }
163            },
164            RenderParam::InitParams(params) => {
165                Box::into_raw(Box::new(libmpv2_sys::mpv_opengl_init_params::from(params)))
166                    as *mut c_void
167            }
168            RenderParam::FBO(fbo) => Box::into_raw(Box::new(fbo)) as *mut c_void,
169            RenderParam::FlipY(flip) => Box::into_raw(Box::new(flip as c_int)) as *mut c_void,
170            RenderParam::Depth(depth) => Box::into_raw(Box::new(depth)) as *mut c_void,
171            RenderParam::ICCProfile(bytes) => {
172                Box::into_raw(bytes.into_boxed_slice()) as *mut c_void
173            }
174            RenderParam::AmbientLight(lux) => Box::into_raw(Box::new(lux)) as *mut c_void,
175            RenderParam::X11Display(ptr) => ptr as *mut _,
176            RenderParam::WaylandDisplay(ptr) => ptr as *mut _,
177            RenderParam::AdvancedControl(adv_ctrl) => {
178                Box::into_raw(Box::new(adv_ctrl as c_int)) as *mut c_void
179            }
180            RenderParam::NextFrameInfo(frame_info) => {
181                Box::into_raw(Box::new(frame_info)) as *mut c_void
182            }
183            RenderParam::BlockForTargetTime(block) => {
184                Box::into_raw(Box::new(block as c_int)) as *mut c_void
185            }
186            RenderParam::SkipRendering(skip_rendering) => {
187                Box::into_raw(Box::new(skip_rendering as c_int)) as *mut c_void
188            }
189        };
190        Self { type_, data }
191    }
192}
193
194unsafe fn free_void_data<T>(ptr: *mut c_void) {
195    drop(unsafe { Box::<T>::from_raw(ptr as *mut T) });
196}
197
198unsafe fn free_init_params<C>(ptr: *mut c_void) {
199    let params = unsafe { Box::from_raw(ptr as *mut libmpv2_sys::mpv_opengl_init_params) };
200    drop(unsafe { Box::from_raw(params.get_proc_address_ctx as *mut OpenGLInitParams<C>) });
201}
202
203impl RenderContext {
204    pub fn new<C>(
205        mpv: &mut libmpv2_sys::mpv_handle,
206        params: impl IntoIterator<Item = RenderParam<C>>,
207    ) -> Result<Self> {
208        let params: Vec<_> = params.into_iter().collect();
209        let mut raw_params: Vec<libmpv2_sys::mpv_render_param> = Vec::new();
210        raw_params.reserve(params.len() + 1);
211        let mut raw_ptrs: HashMap<*const c_void, DeleterFn> = HashMap::new();
212
213        for p in params {
214            // The render params are type-erased after they are passed to mpv. This is where we last
215            // know their real types, so we keep a deleter here.
216            let deleter: Option<DeleterFn> = match p {
217                RenderParam::InitParams(_) => Some(free_init_params::<C>),
218                RenderParam::FBO(_) => Some(free_void_data::<FBO>),
219                RenderParam::FlipY(_) => Some(free_void_data::<i32>),
220                RenderParam::Depth(_) => Some(free_void_data::<i32>),
221                RenderParam::ICCProfile(_) => Some(free_void_data::<Box<[u8]>>),
222                RenderParam::AmbientLight(_) => Some(free_void_data::<i32>),
223                RenderParam::NextFrameInfo(_) => Some(free_void_data::<RenderFrameInfo>),
224                _ => None,
225            };
226            let raw_param: libmpv2_sys::mpv_render_param = p.into();
227            if let Some(deleter) = deleter {
228                raw_ptrs.insert(raw_param.data, deleter);
229            }
230
231            raw_params.push(raw_param);
232        }
233        // the raw array must end with type = 0
234        raw_params.push(libmpv2_sys::mpv_render_param {
235            type_: 0,
236            data: ptr::null_mut(),
237        });
238
239        unsafe {
240            let raw_array =
241                Box::into_raw(raw_params.into_boxed_slice()) as *mut libmpv2_sys::mpv_render_param;
242            let ctx = Box::into_raw(Box::new(std::ptr::null_mut() as _));
243            let err = libmpv2_sys::mpv_render_context_create(ctx, &mut *mpv, raw_array);
244            drop(Box::from_raw(raw_array));
245            for (ptr, deleter) in raw_ptrs.iter() {
246                (deleter)(*ptr as _);
247            }
248
249            mpv_err(
250                Self {
251                    ctx: *Box::from_raw(ctx),
252                    update_callback_cleanup: None,
253                },
254                err,
255            )
256        }
257    }
258
259    pub fn set_parameter<C>(&self, param: RenderParam<C>) -> Result<()> {
260        unsafe {
261            mpv_err(
262                (),
263                libmpv2_sys::mpv_render_context_set_parameter(
264                    self.ctx,
265                    libmpv2_sys::mpv_render_param::from(param),
266                ),
267            )
268        }
269    }
270
271    pub fn get_info<C>(&self, param: RenderParam<C>) -> Result<RenderParam<C>> {
272        let is_next_frame_info = matches!(param, RenderParam::NextFrameInfo(_));
273        let raw_param = libmpv2_sys::mpv_render_param::from(param);
274        let res = unsafe { libmpv2_sys::mpv_render_context_get_info(self.ctx, raw_param) };
275        if res == 0 {
276            if !is_next_frame_info {
277                panic!("I don't know how to handle this info type.");
278            }
279            let raw_frame_info = raw_param.data as *mut libmpv2_sys::mpv_render_frame_info;
280            unsafe {
281                let raw_frame_info = *raw_frame_info;
282                return Ok(RenderParam::NextFrameInfo(RenderFrameInfo {
283                    flags: raw_frame_info.flags.into(),
284                    target_time: raw_frame_info.target_time,
285                }));
286            }
287        }
288        Err(Error::Raw(res))
289    }
290
291    /// Render video.
292    ///
293    /// Typically renders the video to a target surface provided via `fbo`
294    /// (the details depend on the backend in use). Options like "panscan" are
295    /// applied to determine which part of the video should be visible and how the
296    /// video should be scaled. You can change these options at runtime by using the
297    /// mpv property API.
298    ///
299    /// The renderer will reconfigure itself every time the target surface
300    /// configuration (such as size) is changed.
301    ///
302    /// This function implicitly pulls a video frame from the internal queue and
303    /// renders it. If no new frame is available, the previous frame is redrawn.
304    /// The update callback set with [set_update_callback](Self::set_update_callback)
305    /// notifies you when a new frame was added. The details potentially depend on
306    /// the backends and the provided parameters.
307    ///
308    /// Generally, libmpv will invoke your update callback some time before the video
309    /// frame should be shown, and then lets this function block until the supposed
310    /// display time. This will limit your rendering to video FPS. You can prevent
311    /// this by setting the "video-timing-offset" global option to 0. (This applies
312    /// only to "audio" video sync mode.)
313    ///
314    /// # Arguments
315    ///
316    /// * `fbo` - A framebuffer object to render to. In OpenGL, 0 is the current backbuffer
317    /// * `width` - The width of the framebuffer in pixels. This is used for scaling the
318    ///             video properly.
319    /// * `height` - The height of the framebuffer in pixels. This is used for scaling the
320    ///              video properly.
321    /// * `flip` - Whether to draw the image upside down. This is needed for OpenGL because
322    ///            it uses a coordinate system with positive Y up, but videos use positive
323    ///            Y down.
324    pub fn render<GLContext>(&self, fbo: i32, width: i32, height: i32, flip: bool) -> Result<()> {
325        let mut raw_params: Vec<libmpv2_sys::mpv_render_param> = Vec::with_capacity(3);
326        let mut raw_ptrs: HashMap<*const c_void, DeleterFn> = HashMap::new();
327
328        let raw_param: libmpv2_sys::mpv_render_param =
329            RenderParam::<GLContext>::FBO(FBO { fbo, width, height }).into();
330        raw_ptrs.insert(raw_param.data, free_void_data::<FBO>);
331        raw_params.push(raw_param);
332        let raw_param: libmpv2_sys::mpv_render_param = RenderParam::<GLContext>::FlipY(flip).into();
333        raw_ptrs.insert(raw_param.data, free_void_data::<i32>);
334        raw_params.push(raw_param);
335        // the raw array must end with type = 0
336        raw_params.push(libmpv2_sys::mpv_render_param {
337            type_: 0,
338            data: ptr::null_mut(),
339        });
340
341        let raw_array =
342            Box::into_raw(raw_params.into_boxed_slice()) as *mut libmpv2_sys::mpv_render_param;
343
344        let ret = unsafe {
345            mpv_err(
346                (),
347                libmpv2_sys::mpv_render_context_render(self.ctx, raw_array),
348            )
349        };
350        unsafe {
351            drop(Box::from_raw(raw_array));
352        }
353
354        unsafe {
355            for (ptr, deleter) in raw_ptrs.iter() {
356                (deleter)(*ptr as _);
357            }
358        }
359
360        ret
361    }
362
363    /// Tell the renderer that a frame was flipped at the given time. This is
364    /// optional, but can help the player to achieve better timing.
365    ///
366    /// Note that calling this at least once informs libmpv that you will use this
367    /// function. If you use it inconsistently, expect bad video playback.
368    ///
369    /// If this is called while no video is initialized, it is ignored.
370    pub fn report_swap(&self) {
371        unsafe { libmpv2_sys::mpv_render_context_report_swap(self.ctx) }
372    }
373
374    /// Set the callback that notifies you when a new video frame is available, or if the video display
375    /// configuration somehow changed and requires a redraw. Similar to [EventContext::set_wakeup_callback](crate::events::EventContext::set_wakeup_callback), you
376    /// must not call any mpv API from the callback, and all the other listed restrictions apply (such
377    /// as not exiting the callback by throwing exceptions).
378    ///
379    /// This can be called from any thread, except from an update callback. In case of the OpenGL backend,
380    /// no OpenGL state or API is accessed.
381    ///
382    /// Calling this will raise an update callback immediately.
383    pub fn set_update_callback<F: Fn() + Send + 'static>(&mut self, callback: F) {
384        if let Some(update_callback_cleanup) = self.update_callback_cleanup.take() {
385            update_callback_cleanup();
386        }
387        let raw_callback = Box::into_raw(Box::new(callback));
388        self.update_callback_cleanup = Some(Box::new(move || unsafe {
389            drop(Box::from_raw(raw_callback));
390        }) as Box<dyn FnOnce()>);
391        unsafe {
392            libmpv2_sys::mpv_render_context_set_update_callback(
393                self.ctx,
394                Some(ru_wrapper::<F>),
395                raw_callback as *mut c_void,
396            );
397        }
398    }
399
400    /// The API user is supposed to call this when the update callback was invoked
401    /// (like all mpv_render_* functions, this has to happen on the render thread,
402    /// and _not_ from the update callback itself).
403    ///
404    /// This is optional if MPV_RENDER_PARAM_ADVANCED_CONTROL was not set (default).
405    /// Otherwise, it's a hard requirement that this is called after each update
406    /// callback. If multiple update callback happened, and the function could not
407    /// be called sooner, it's OK to call it once after the last callback.
408    ///
409    /// If an update callback happens during or after this function, the function
410    /// must be called again at the soonest possible time.
411    ///
412    /// If MPV_RENDER_PARAM_ADVANCED_CONTROL was set, this will do additional work
413    /// such as allocating textures for the video decoder.
414    ///
415    /// # Returns
416    ///
417    /// A bitset of mpv_render_update_flag values (i.e. multiple flags are
418    /// combined with bitwise or). Typically, this will tell the API user
419    /// what should happen next. E.g. if the MPV_RENDER_UPDATE_FRAME flag is
420    /// set, mpv_render_context_render() should be called. If flags unknown
421    /// to the API user are set, or if the return value is 0, nothing needs
422    /// to be done.
423    pub fn update(&self) -> Result<MpvRenderUpdate> {
424        let res = unsafe { libmpv2_sys::mpv_render_context_update(self.ctx) };
425        match res.try_into() {
426            Ok(res) => Ok(res),
427            Err(_) => Err(Error::Raw(libmpv2_sys::mpv_error_MPV_ERROR_GENERIC)),
428        }
429    }
430}
431
432impl Drop for RenderContext {
433    fn drop(&mut self) {
434        if let Some(update_callback_cleanup) = self.update_callback_cleanup.take() {
435            update_callback_cleanup();
436        }
437        unsafe {
438            libmpv2_sys::mpv_render_context_free(self.ctx);
439        }
440    }
441}