mod.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2026 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use async_trait::async_trait;
  19. use parking_lot::Mutex as SyncMutex;
  20. use rand::{rngs::OsRng, Rng};
  21. use std::sync::Arc;
  22. use tracing::instrument;
  23. use crate::{
  24. gfx::{
  25. anim::Frame, gfxtag, DrawCall, DrawInstruction, DrawMesh, EpochCache, GraphicPipeline,
  26. ManagedSeqAnimPtr, ManagedTexturePtr, Rectangle, RenderApi, Renderer,
  27. },
  28. mesh::{MeshBuilder, MeshInfo, COLOR_WHITE},
  29. prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyRect, PropertyStr, PropertyUint32, Role},
  30. scene::{Pimpl, SceneNodeWeak},
  31. ExecutorPtr,
  32. };
  33. use super::{DrawUpdate, OnModify, RedrawTrigger, UIObject};
  34. mod decode;
  35. #[allow(dead_code)]
  36. mod ivf;
  37. use decode::spawn_decoder_thread;
  38. pub type VideoPtr = Arc<Video>;
  39. #[derive(Clone)]
  40. pub struct YuvTextures {
  41. y: ManagedTexturePtr,
  42. u: ManagedTexturePtr,
  43. v: ManagedTexturePtr,
  44. }
  45. #[derive(Clone)]
  46. pub struct Av1VideoData {
  47. textures: Vec<Option<YuvTextures>>,
  48. anim: ManagedSeqAnimPtr,
  49. textures_pub: async_broadcast::Sender<(usize, YuvTextures)>,
  50. textures_sub: async_broadcast::Receiver<(usize, YuvTextures)>,
  51. }
  52. impl Av1VideoData {
  53. fn new(len: usize, renderer: &Renderer) -> Self {
  54. let (textures_pub, textures_sub) = async_broadcast::broadcast(len);
  55. let anim = renderer.new_anim(len, false, gfxtag!("video"));
  56. Self { textures: vec![None; len], anim, textures_pub, textures_sub }
  57. }
  58. }
  59. pub struct Video {
  60. node: SceneNodeWeak,
  61. renderer: Renderer,
  62. redraw: RedrawTrigger,
  63. tasks: SyncMutex<Vec<smol::Task<()>>>,
  64. load_tasks: SyncMutex<Vec<smol::Task<()>>>,
  65. ex: ExecutorPtr,
  66. dc_key: u64,
  67. vid_data: Arc<SyncMutex<Option<Av1VideoData>>>,
  68. _load_handle: SyncMutex<Option<std::thread::JoinHandle<()>>>,
  69. _decoder_handle: SyncMutex<Option<std::thread::JoinHandle<()>>>,
  70. rect: PropertyRect,
  71. uv: PropertyRect,
  72. z_index: PropertyUint32,
  73. priority: PropertyUint32,
  74. path: PropertyStr,
  75. /// Cached draw instructions. Empty means stale. Entries from a dead
  76. /// UI epoch are evicted automatically.
  77. draw_cache: EpochCache<Vec<DrawInstruction>>,
  78. parent_rect: SyncMutex<Option<Rectangle>>,
  79. }
  80. impl Video {
  81. pub async fn new(
  82. node: SceneNodeWeak,
  83. renderer: Renderer,
  84. redraw: RedrawTrigger,
  85. ex: ExecutorPtr,
  86. ) -> Pimpl {
  87. let node_ref = &node.upgrade().unwrap();
  88. let rect = PropertyRect::wrap(node_ref, Role::Internal, "rect").unwrap();
  89. let uv = PropertyRect::wrap(node_ref, Role::Internal, "uv").unwrap();
  90. let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();
  91. let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
  92. let path = PropertyStr::wrap(node_ref, Role::Internal, "path", 0).unwrap();
  93. let draw_cache = EpochCache::new(&renderer);
  94. let self_ = Arc::new(Self {
  95. node,
  96. renderer,
  97. redraw,
  98. tasks: SyncMutex::new(vec![]),
  99. load_tasks: SyncMutex::new(vec![]),
  100. ex,
  101. dc_key: OsRng.gen(),
  102. vid_data: Arc::new(SyncMutex::new(None)),
  103. _load_handle: SyncMutex::new(None),
  104. _decoder_handle: SyncMutex::new(None),
  105. rect,
  106. uv,
  107. z_index,
  108. priority,
  109. path,
  110. draw_cache,
  111. parent_rect: SyncMutex::new(None),
  112. });
  113. Pimpl::Video(self_)
  114. }
  115. async fn reload(self_: Arc<Self>, _batch: BatchGuardPtr) {
  116. self_.load_video();
  117. self_.draw_cache.clear();
  118. self_.redraw.trigger();
  119. }
  120. fn load_video(&self) {
  121. let path = self.path.get();
  122. // Decoder thread:
  123. // loads path, decodes AV1 -> RGB, creates textures directly
  124. let decoder_handle =
  125. spawn_decoder_thread(path, self.vid_data.clone(), self.renderer.clone());
  126. *self._decoder_handle.lock() = Some(decoder_handle);
  127. }
  128. fn regen_mesh(&self) -> MeshInfo {
  129. let rect = self.rect.get();
  130. let uv = self.uv.get();
  131. let mesh_rect = Rectangle::from([0., 0., rect.w, rect.h]);
  132. let mut mesh = MeshBuilder::new(gfxtag!("img"));
  133. mesh.draw_box(&mesh_rect, COLOR_WHITE, &uv);
  134. mesh.alloc(&self.renderer)
  135. }
  136. /// Wire decoder output into the renderer anim and build the video
  137. /// instructions. Called only when the draw cache is stale or the
  138. /// rect changed; the anim then advances renderer-side.
  139. fn make_instrs(&self, rect: &Rectangle) -> Option<Vec<DrawInstruction>> {
  140. let mesh = self.regen_mesh();
  141. let (vid_data, tsubs) = {
  142. let vid_data_lock = self.vid_data.lock();
  143. let Some(vid_data) = vid_data_lock.as_ref() else {
  144. // Video not loaded yet, skip draw
  145. return None;
  146. };
  147. let tsubs = vec![vid_data.textures_sub.clone(); vid_data.textures.len()];
  148. // Clone the data before the lock is released
  149. (vid_data.clone(), tsubs)
  150. };
  151. assert_eq!(vid_data.textures.len(), tsubs.len());
  152. let mut load_tasks = self.load_tasks.lock();
  153. load_tasks.clear();
  154. let mut loaded_n_frames = 0;
  155. let total_frames = vid_data.textures.len();
  156. for (tex_idx, (mut tex, mut tsub)) in
  157. vid_data.textures.into_iter().zip(tsubs.into_iter()).enumerate()
  158. {
  159. let vertex_buffer = mesh.vertex_buffer.clone();
  160. let index_buffer = mesh.index_buffer.clone();
  161. let Some(tex) = tex.take() else {
  162. let anim = vid_data.anim.clone();
  163. let task = self.ex.spawn(async move {
  164. while let Ok((frame_idx, tex)) = tsub.recv().await {
  165. if frame_idx != tex_idx {
  166. continue
  167. }
  168. let mesh = DrawMesh {
  169. vertex_buffer,
  170. index_buffer,
  171. textures: Some(vec![tex.y, tex.u, tex.v]),
  172. num_elements: mesh.num_elements,
  173. };
  174. let dc = DrawCall {
  175. instrs: vec![DrawInstruction::Draw(mesh)],
  176. dcs: vec![],
  177. z_index: 0,
  178. debug_str: "video",
  179. };
  180. anim.update(frame_idx, Frame::new(40, dc));
  181. break
  182. }
  183. });
  184. load_tasks.push(task);
  185. continue
  186. };
  187. let mesh = DrawMesh {
  188. vertex_buffer,
  189. index_buffer,
  190. textures: Some(vec![tex.y, tex.u, tex.v]),
  191. num_elements: mesh.num_elements,
  192. };
  193. let dc = DrawCall {
  194. instrs: vec![DrawInstruction::Draw(mesh)],
  195. dcs: vec![],
  196. z_index: 0,
  197. debug_str: "video",
  198. };
  199. vid_data.anim.update(tex_idx, Frame::new(40, dc));
  200. loaded_n_frames += 1;
  201. }
  202. debug!(target: "ui::video", "Loaded {loaded_n_frames} / {total_frames} frames");
  203. Some(vec![
  204. DrawInstruction::SetPipeline(GraphicPipeline::YUV),
  205. DrawInstruction::Move(rect.pos()),
  206. DrawInstruction::Animation(vid_data.anim.clone()),
  207. ])
  208. }
  209. }
  210. #[async_trait]
  211. impl UIObject for Video {
  212. fn priority(&self) -> u32 {
  213. self.priority.get()
  214. }
  215. fn init(&self) {
  216. // Drop textures from a dead UI epoch (if any) before reloading
  217. *self.vid_data.lock() = None;
  218. self.load_video();
  219. }
  220. async fn start(self: Arc<Self>, ex: ExecutorPtr) {
  221. let me = Arc::downgrade(&self);
  222. let mut on_modify = OnModify::new(ex, self.node.clone(), me.clone());
  223. on_modify.when_change_external(self.rect.prop(), |self_, _| async move {
  224. self_.draw_cache.clear();
  225. self_.redraw.trigger();
  226. });
  227. on_modify.when_change_external(self.uv.prop(), |self_, _| async move {
  228. self_.draw_cache.clear();
  229. self_.redraw.trigger();
  230. });
  231. on_modify.when_change_external(self.z_index.prop(), |self_, _| async move {
  232. self_.draw_cache.clear();
  233. self_.redraw.trigger();
  234. });
  235. on_modify.when_change(self.path.prop(), Self::reload);
  236. *self.tasks.lock() = on_modify.tasks;
  237. }
  238. fn stop(&self) {
  239. self.tasks.lock().clear();
  240. *self.parent_rect.lock() = None;
  241. *self.vid_data.lock() = None;
  242. self.draw_cache.clear();
  243. // Threads terminate naturally when channels close
  244. }
  245. #[instrument(target = "ui::video")]
  246. async fn draw(
  247. &self,
  248. parent_rect: Rectangle,
  249. atom: &mut PropertyAtomicGuard,
  250. ) -> Option<DrawUpdate> {
  251. *self.parent_rect.lock() = Some(parent_rect);
  252. // Rect property is its own memo: compare before/after eval.
  253. let prev_rect = self.rect.get();
  254. self.rect.eval(atom, &parent_rect).ok()?;
  255. let rect = self.rect.get();
  256. let rect_changed = rect != prev_rect;
  257. self.uv.eval(atom, &rect).ok()?;
  258. // Compute under the cache lock so a concurrent invalidation lands
  259. // before or after, never between. A video that has not loaded
  260. // yet stays uncached so the next pass retries.
  261. if rect_changed {
  262. self.draw_cache.clear();
  263. }
  264. let instrs = match self.draw_cache.get() {
  265. Some(instrs) => instrs,
  266. None => {
  267. let Some(instrs) = self.make_instrs(&rect) else { return None };
  268. self.draw_cache.set(instrs.clone());
  269. instrs
  270. }
  271. };
  272. Some(DrawUpdate {
  273. key: self.dc_key,
  274. draw_calls: vec![(
  275. self.dc_key,
  276. DrawCall::new(instrs, vec![], self.z_index.get(), "vid"),
  277. )],
  278. })
  279. }
  280. }
  281. impl Drop for Video {
  282. fn drop(&mut self) {
  283. self.renderer.replace_draw_calls(vec![(self.dc_key, Default::default())]);
  284. }
  285. }
  286. impl std::fmt::Debug for Video {
  287. fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
  288. write!(f, "{:?}", self.node.upgrade().unwrap())
  289. }
  290. }