about summary refs log tree commit diff stats
path: root/crates/libmpv2/src/mpv/render.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-23 12:57:19 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-08-23 12:58:02 +0200
commit0ae5018c33cc4bfe27583c9902472b499f4bd269 (patch)
treeafc2fbfcb126215f47afbc32e555d203d4d6d88c /crates/libmpv2/src/mpv/render.rs
parentchore(yt_dlp/progress_hook): Also consider the `total_bytes_estimate` field (diff)
downloadyt-0ae5018c33cc4bfe27583c9902472b499f4bd269.tar.gz
yt-0ae5018c33cc4bfe27583c9902472b499f4bd269.zip
refactor(libmpv2): Move to the `crates` directory
Diffstat (limited to 'crates/libmpv2/src/mpv/render.rs')
-rw-r--r--crates/libmpv2/src/mpv/render.rs406
1 files changed, 406 insertions, 0 deletions
diff --git a/crates/libmpv2/src/mpv/render.rs b/crates/libmpv2/src/mpv/render.rs
new file mode 100644
index 0000000..91db34e
--- /dev/null
+++ b/crates/libmpv2/src/mpv/render.rs
@@ -0,0 +1,406 @@
+// yt - A fully featured command line YouTube client
+//
+// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// This file is part of Yt.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+use crate::{mpv::mpv_err, Error, Result};
+use std::collections::HashMap;
+use std::ffi::{c_void, CStr};
+use std::os::raw::c_int;
+use std::ptr;
+
+type DeleterFn = unsafe fn(*mut c_void);
+
+pub struct RenderContext {
+    ctx: *mut libmpv2_sys::mpv_render_context,
+    update_callback_cleanup: Option<Box<dyn FnOnce()>>,
+}
+
+/// For initializing the mpv OpenGL state via RenderParam::OpenGLInitParams
+pub struct OpenGLInitParams<GLContext> {
+    /// This retrieves OpenGL function pointers, and will use them in subsequent
+    /// operation.
+    /// Usually, you can simply call the GL context APIs from this callback (e.g.
+    /// glXGetProcAddressARB or wglGetProcAddress), but some APIs do not always
+    /// return pointers for all standard functions (even if present); in this
+    /// case you have to compensate by looking up these functions yourself when
+    /// libmpv wants to resolve them through this callback.
+    /// libmpv will not normally attempt to resolve GL functions on its own, nor
+    /// does it link to GL libraries directly.
+    pub get_proc_address: fn(ctx: &GLContext, name: &str) -> *mut c_void,
+
+    /// Value passed as ctx parameter to get_proc_address().
+    pub ctx: GLContext,
+}
+
+/// For RenderParam::FBO
+pub struct FBO {
+    pub fbo: i32,
+    pub width: i32,
+    pub height: i32,
+}
+
+#[repr(u32)]
+#[derive(Clone)]
+pub enum RenderFrameInfoFlag {
+    Present = libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_PRESENT,
+    Redraw = libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_REDRAW,
+    Repeat = libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_REPEAT,
+    BlockVSync = libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_BLOCK_VSYNC,
+}
+
+impl From<u64> for RenderFrameInfoFlag {
+    // mpv_render_frame_info_flag is u32, but mpv_render_frame_info.flags is u64 o\
+    fn from(val: u64) -> Self {
+        let val = val as u32;
+        match val {
+            libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_PRESENT => {
+                RenderFrameInfoFlag::Present
+            }
+            libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_REDRAW => {
+                RenderFrameInfoFlag::Redraw
+            }
+            libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_REPEAT => {
+                RenderFrameInfoFlag::Repeat
+            }
+            libmpv2_sys::mpv_render_frame_info_flag_MPV_RENDER_FRAME_INFO_BLOCK_VSYNC => {
+                RenderFrameInfoFlag::BlockVSync
+            }
+            _ => panic!("Tried converting invalid value to RenderFrameInfoFlag"),
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct RenderFrameInfo {
+    pub flags: RenderFrameInfoFlag,
+    pub target_time: i64,
+}
+
+pub enum RenderParamApiType {
+    OpenGl,
+}
+
+pub enum RenderParam<GLContext> {
+    Invalid,
+    ApiType(RenderParamApiType),
+    InitParams(OpenGLInitParams<GLContext>),
+    FBO(FBO),
+    FlipY(bool),
+    Depth(i32),
+    ICCProfile(Vec<u8>),
+    AmbientLight(i32),
+    X11Display(*const c_void),
+    WaylandDisplay(*const c_void),
+    AdvancedControl(bool),
+    NextFrameInfo(RenderFrameInfo),
+    BlockForTargetTime(bool),
+    SkipRendering(bool),
+}
+
+impl<C> From<&RenderParam<C>> for u32 {
+    fn from(val: &RenderParam<C>) -> Self {
+        match val {
+            RenderParam::Invalid => 0,
+            RenderParam::ApiType(_) => 1,
+            RenderParam::InitParams(_) => 2,
+            RenderParam::FBO(_) => 3,
+            RenderParam::FlipY(_) => 4,
+            RenderParam::Depth(_) => 5,
+            RenderParam::ICCProfile(_) => 6,
+            RenderParam::AmbientLight(_) => 7,
+            RenderParam::X11Display(_) => 8,
+            RenderParam::WaylandDisplay(_) => 9,
+            RenderParam::AdvancedControl(_) => 10,
+            RenderParam::NextFrameInfo(_) => 11,
+            RenderParam::BlockForTargetTime(_) => 12,
+            RenderParam::SkipRendering(_) => 13,
+        }
+    }
+}
+
+unsafe extern "C" fn gpa_wrapper<GLContext>(ctx: *mut c_void, name: *const i8) -> *mut c_void {
+    if ctx.is_null() {
+        panic!("ctx for get_proc_address wrapper is NULL");
+    }
+
+    let params: *mut OpenGLInitParams<GLContext> = ctx as _;
+    let params = &*params;
+    (params.get_proc_address)(
+        &params.ctx,
+        CStr::from_ptr(name)
+            .to_str()
+            .expect("Could not convert function name to str"),
+    )
+}
+
+unsafe extern "C" fn ru_wrapper<F: Fn() + Send + 'static>(ctx: *mut c_void) {
+    if ctx.is_null() {
+        panic!("ctx for render_update wrapper is NULL");
+    }
+
+    (*(ctx as *mut F))();
+}
+
+impl<C> From<OpenGLInitParams<C>> for libmpv2_sys::mpv_opengl_init_params {
+    fn from(val: OpenGLInitParams<C>) -> Self {
+        Self {
+            get_proc_address: Some(gpa_wrapper::<OpenGLInitParams<C>>),
+            get_proc_address_ctx: Box::into_raw(Box::new(val)) as *mut c_void,
+        }
+    }
+}
+
+impl<C> From<RenderParam<C>> for libmpv2_sys::mpv_render_param {
+    fn from(val: RenderParam<C>) -> Self {
+        let type_ = u32::from(&val);
+        let data = match val {
+            RenderParam::Invalid => ptr::null_mut(),
+            RenderParam::ApiType(api_type) => match api_type {
+                RenderParamApiType::OpenGl => {
+                    libmpv2_sys::MPV_RENDER_API_TYPE_OPENGL.as_ptr() as *mut c_void
+                }
+            },
+            RenderParam::InitParams(params) => {
+                Box::into_raw(Box::new(libmpv2_sys::mpv_opengl_init_params::from(params)))
+                    as *mut c_void
+            }
+            RenderParam::FBO(fbo) => Box::into_raw(Box::new(fbo)) as *mut c_void,
+            RenderParam::FlipY(flip) => Box::into_raw(Box::new(flip as c_int)) as *mut c_void,
+            RenderParam::Depth(depth) => Box::into_raw(Box::new(depth)) as *mut c_void,
+            RenderParam::ICCProfile(bytes) => {
+                Box::into_raw(bytes.into_boxed_slice()) as *mut c_void
+            }
+            RenderParam::AmbientLight(lux) => Box::into_raw(Box::new(lux)) as *mut c_void,
+            RenderParam::X11Display(ptr) => ptr as *mut _,
+            RenderParam::WaylandDisplay(ptr) => ptr as *mut _,
+            RenderParam::AdvancedControl(adv_ctrl) => {
+                Box::into_raw(Box::new(adv_ctrl as c_int)) as *mut c_void
+            }
+            RenderParam::NextFrameInfo(frame_info) => {
+                Box::into_raw(Box::new(frame_info)) as *mut c_void
+            }
+            RenderParam::BlockForTargetTime(block) => {
+                Box::into_raw(Box::new(block as c_int)) as *mut c_void
+            }
+            RenderParam::SkipRendering(skip_rendering) => {
+                Box::into_raw(Box::new(skip_rendering as c_int)) as *mut c_void
+            }
+        };
+        Self { type_, data }
+    }
+}
+
+unsafe fn free_void_data<T>(ptr: *mut c_void) {
+    drop(Box::<T>::from_raw(ptr as *mut T));
+}
+
+unsafe fn free_init_params<C>(ptr: *mut c_void) {
+    let params = Box::from_raw(ptr as *mut libmpv2_sys::mpv_opengl_init_params);
+    drop(Box::from_raw(
+        params.get_proc_address_ctx as *mut OpenGLInitParams<C>,
+    ));
+}
+
+impl RenderContext {
+    pub fn new<C>(
+        mpv: &mut libmpv2_sys::mpv_handle,
+        params: impl IntoIterator<Item = RenderParam<C>>,
+    ) -> Result<Self> {
+        let params: Vec<_> = params.into_iter().collect();
+        let mut raw_params: Vec<libmpv2_sys::mpv_render_param> = Vec::new();
+        raw_params.reserve(params.len() + 1);
+        let mut raw_ptrs: HashMap<*const c_void, DeleterFn> = HashMap::new();
+
+        for p in params {
+            // The render params are type-erased after they are passed to mpv. This is where we last
+            // know their real types, so we keep a deleter here.
+            let deleter: Option<DeleterFn> = match p {
+                RenderParam::InitParams(_) => Some(free_init_params::<C>),
+                RenderParam::FBO(_) => Some(free_void_data::<FBO>),
+                RenderParam::FlipY(_) => Some(free_void_data::<i32>),
+                RenderParam::Depth(_) => Some(free_void_data::<i32>),
+                RenderParam::ICCProfile(_) => Some(free_void_data::<Box<[u8]>>),
+                RenderParam::AmbientLight(_) => Some(free_void_data::<i32>),
+                RenderParam::NextFrameInfo(_) => Some(free_void_data::<RenderFrameInfo>),
+                _ => None,
+            };
+            let raw_param: libmpv2_sys::mpv_render_param = p.into();
+            if let Some(deleter) = deleter {
+                raw_ptrs.insert(raw_param.data, deleter);
+            }
+
+            raw_params.push(raw_param);
+        }
+        // the raw array must end with type = 0
+        raw_params.push(libmpv2_sys::mpv_render_param {
+            type_: 0,
+            data: ptr::null_mut(),
+        });
+
+        unsafe {
+            let raw_array =
+                Box::into_raw(raw_params.into_boxed_slice()) as *mut libmpv2_sys::mpv_render_param;
+            let ctx = Box::into_raw(Box::new(std::ptr::null_mut() as _));
+            let err = libmpv2_sys::mpv_render_context_create(ctx, &mut *mpv, raw_array);
+            drop(Box::from_raw(raw_array));
+            for (ptr, deleter) in raw_ptrs.iter() {
+                (deleter)(*ptr as _);
+            }
+
+            mpv_err(
+                Self {
+                    ctx: *Box::from_raw(ctx),
+                    update_callback_cleanup: None,
+                },
+                err,
+            )
+        }
+    }
+
+    pub fn set_parameter<C>(&self, param: RenderParam<C>) -> Result<()> {
+        unsafe {
+            mpv_err(
+                (),
+                libmpv2_sys::mpv_render_context_set_parameter(
+                    self.ctx,
+                    libmpv2_sys::mpv_render_param::from(param),
+                ),
+            )
+        }
+    }
+
+    pub fn get_info<C>(&self, param: RenderParam<C>) -> Result<RenderParam<C>> {
+        let is_next_frame_info = matches!(param, RenderParam::NextFrameInfo(_));
+        let raw_param = libmpv2_sys::mpv_render_param::from(param);
+        let res = unsafe { libmpv2_sys::mpv_render_context_get_info(self.ctx, raw_param) };
+        if res == 0 {
+            if !is_next_frame_info {
+                panic!("I don't know how to handle this info type.");
+            }
+            let raw_frame_info = raw_param.data as *mut libmpv2_sys::mpv_render_frame_info;
+            unsafe {
+                let raw_frame_info = *raw_frame_info;
+                return Ok(RenderParam::NextFrameInfo(RenderFrameInfo {
+                    flags: raw_frame_info.flags.into(),
+                    target_time: raw_frame_info.target_time,
+                }));
+            }
+        }
+        Err(Error::Raw(res))
+    }
+
+    /// Render video.
+    ///
+    /// Typically renders the video to a target surface provided via `fbo`
+    /// (the details depend on the backend in use). Options like "panscan" are
+    /// applied to determine which part of the video should be visible and how the
+    /// video should be scaled. You can change these options at runtime by using the
+    /// mpv property API.
+    ///
+    /// The renderer will reconfigure itself every time the target surface
+    /// configuration (such as size) is changed.
+    ///
+    /// This function implicitly pulls a video frame from the internal queue and
+    /// renders it. If no new frame is available, the previous frame is redrawn.
+    /// The update callback set with [set_update_callback](Self::set_update_callback)
+    /// notifies you when a new frame was added. The details potentially depend on
+    /// the backends and the provided parameters.
+    ///
+    /// Generally, libmpv will invoke your update callback some time before the video
+    /// frame should be shown, and then lets this function block until the supposed
+    /// display time. This will limit your rendering to video FPS. You can prevent
+    /// this by setting the "video-timing-offset" global option to 0. (This applies
+    /// only to "audio" video sync mode.)
+    ///
+    /// # Arguments
+    ///
+    /// * `fbo` - A framebuffer object to render to. In OpenGL, 0 is the current backbuffer
+    /// * `width` - The width of the framebuffer in pixels. This is used for scaling the
+    ///             video properly.
+    /// * `height` - The height of the framebuffer in pixels. This is used for scaling the
+    ///              video properly.
+    /// * `flip` - Whether to draw the image upside down. This is needed for OpenGL because
+    ///            it uses a coordinate system with positive Y up, but videos use positive
+    ///            Y down.
+    pub fn render<GLContext>(&self, fbo: i32, width: i32, height: i32, flip: bool) -> Result<()> {
+        let mut raw_params: Vec<libmpv2_sys::mpv_render_param> = Vec::with_capacity(3);
+        let mut raw_ptrs: HashMap<*const c_void, DeleterFn> = HashMap::new();
+
+        let raw_param: libmpv2_sys::mpv_render_param =
+            RenderParam::<GLContext>::FBO(FBO { fbo, width, height }).into();
+        raw_ptrs.insert(raw_param.data, free_void_data::<FBO>);
+        raw_params.push(raw_param);
+        let raw_param: libmpv2_sys::mpv_render_param = RenderParam::<GLContext>::FlipY(flip).into();
+        raw_ptrs.insert(raw_param.data, free_void_data::<i32>);
+        raw_params.push(raw_param);
+        // the raw array must end with type = 0
+        raw_params.push(libmpv2_sys::mpv_render_param {
+            type_: 0,
+            data: ptr::null_mut(),
+        });
+
+        let raw_array =
+            Box::into_raw(raw_params.into_boxed_slice()) as *mut libmpv2_sys::mpv_render_param;
+
+        let ret = unsafe {
+            mpv_err(
+                (),
+                libmpv2_sys::mpv_render_context_render(self.ctx, raw_array),
+            )
+        };
+        unsafe {
+            drop(Box::from_raw(raw_array));
+        }
+
+        unsafe {
+            for (ptr, deleter) in raw_ptrs.iter() {
+                (deleter)(*ptr as _);
+            }
+        }
+
+        ret
+    }
+
+    /// Set the callback that notifies you when a new video frame is available, or if the video display
+    /// configuration somehow changed and requires a redraw. Similar to [EventContext::set_wakeup_callback](crate::events::EventContext::set_wakeup_callback), you
+    /// must not call any mpv API from the callback, and all the other listed restrictions apply (such
+    /// as not exiting the callback by throwing exceptions).
+    ///
+    /// This can be called from any thread, except from an update callback. In case of the OpenGL backend,
+    /// no OpenGL state or API is accessed.
+    ///
+    /// Calling this will raise an update callback immediately.
+    pub fn set_update_callback<F: Fn() + Send + 'static>(&mut self, callback: F) {
+        if let Some(update_callback_cleanup) = self.update_callback_cleanup.take() {
+            update_callback_cleanup();
+        }
+        let raw_callback = Box::into_raw(Box::new(callback));
+        self.update_callback_cleanup = Some(Box::new(move || unsafe {
+            drop(Box::from_raw(raw_callback));
+        }) as Box<dyn FnOnce()>);
+        unsafe {
+            libmpv2_sys::mpv_render_context_set_update_callback(
+                self.ctx,
+                Some(ru_wrapper::<F>),
+                raw_callback as *mut c_void,
+            );
+        }
+    }
+}
+
+impl Drop for RenderContext {
+    fn drop(&mut self) {
+        if let Some(update_callback_cleanup) = self.update_callback_cleanup.take() {
+            update_callback_cleanup();
+        }
+        unsafe {
+            libmpv2_sys::mpv_render_context_free(self.ctx);
+        }
+    }
+}