diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-23 12:57:19 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-23 12:58:02 +0200 |
commit | 0ae5018c33cc4bfe27583c9902472b499f4bd269 (patch) | |
tree | afc2fbfcb126215f47afbc32e555d203d4d6d88c /crates/libmpv2/src/mpv/render.rs | |
parent | chore(yt_dlp/progress_hook): Also consider the `total_bytes_estimate` field (diff) | |
download | yt-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.rs | 406 |
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)( + ¶ms.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); + } + } +} |