|
|
@@ -18,7 +18,7 @@
|
|
|
|
|
|
use async_trait::async_trait;
|
|
|
use image::ImageReader;
|
|
|
-use parking_lot::Mutex as SyncMutex;
|
|
|
+use parking_lot::{Mutex as SyncMutex, RwLock};
|
|
|
use rand::{rngs::OsRng, Rng};
|
|
|
use std::{
|
|
|
io::Cursor,
|
|
|
@@ -30,7 +30,7 @@ use std::{
|
|
|
|
|
|
use crate::{
|
|
|
gfx::{
|
|
|
- anim::{SequenceAnimation, SequenceAnimationFrame},
|
|
|
+ anim::{Frame, SeqAnim},
|
|
|
gfxtag, DrawCall, DrawInstruction, DrawMesh, ManagedTexturePtr, Rectangle, RenderApi,
|
|
|
},
|
|
|
mesh::{MeshBuilder, MeshInfo, COLOR_WHITE},
|
|
|
@@ -42,6 +42,8 @@ use crate::{
|
|
|
|
|
|
use super::{DrawTrace, DrawUpdate, OnModify, UIObject};
|
|
|
|
|
|
+pub const N_LOADERS: usize = 4;
|
|
|
+
|
|
|
macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::video", $($arg)*); } }
|
|
|
|
|
|
pub type VideoPtr = Arc<Video>;
|
|
|
@@ -50,23 +52,29 @@ pub struct Video {
|
|
|
node: SceneNodeWeak,
|
|
|
render_api: RenderApi,
|
|
|
tasks: SyncMutex<Vec<smol::Task<()>>>,
|
|
|
+ load_tasks: SyncMutex<Vec<smol::Task<()>>>,
|
|
|
+ ex: ExecutorPtr,
|
|
|
stop_load: Arc<AtomicBool>,
|
|
|
-
|
|
|
- textures: SyncMutex<Vec<ManagedTexturePtr>>,
|
|
|
dc_key: u64,
|
|
|
|
|
|
+ textures_pub: async_broadcast::Sender<(usize, ManagedTexturePtr)>,
|
|
|
+ textures_sub: async_broadcast::Receiver<(usize, ManagedTexturePtr)>,
|
|
|
+ textures: Arc<SyncMutex<Vec<Option<ManagedTexturePtr>>>>,
|
|
|
+ // Do we need this?
|
|
|
+ _load_handles: SyncMutex<[Option<std::thread::JoinHandle<()>>; N_LOADERS]>,
|
|
|
+
|
|
|
rect: PropertyRect,
|
|
|
uv: PropertyRect,
|
|
|
z_index: PropertyUint32,
|
|
|
priority: PropertyUint32,
|
|
|
path: PropertyStr,
|
|
|
- len: PropertyUint32,
|
|
|
+ vid_len: PropertyUint32,
|
|
|
|
|
|
parent_rect: SyncMutex<Option<Rectangle>>,
|
|
|
}
|
|
|
|
|
|
impl Video {
|
|
|
- pub async fn new(node: SceneNodeWeak, render_api: RenderApi) -> Pimpl {
|
|
|
+ pub async fn new(node: SceneNodeWeak, render_api: RenderApi, ex: ExecutorPtr) -> Pimpl {
|
|
|
t!("Video::new()");
|
|
|
|
|
|
let node_ref = &node.upgrade().unwrap();
|
|
|
@@ -75,23 +83,30 @@ impl Video {
|
|
|
let z_index = PropertyUint32::wrap(node_ref, Role::Internal, "z_index", 0).unwrap();
|
|
|
let priority = PropertyUint32::wrap(node_ref, Role::Internal, "priority", 0).unwrap();
|
|
|
let path = PropertyStr::wrap(node_ref, Role::Internal, "path", 0).unwrap();
|
|
|
- let len = PropertyUint32::wrap(node_ref, Role::Internal, "length", 0).unwrap();
|
|
|
+ let vid_len = PropertyUint32::wrap(node_ref, Role::Internal, "length", 0).unwrap();
|
|
|
+
|
|
|
+ let (textures_pub, textures_sub) = async_broadcast::broadcast(1);
|
|
|
|
|
|
let self_ = Arc::new(Self {
|
|
|
node,
|
|
|
render_api,
|
|
|
tasks: SyncMutex::new(vec![]),
|
|
|
+ load_tasks: SyncMutex::new(vec![]),
|
|
|
+ ex,
|
|
|
stop_load: Arc::new(AtomicBool::new(false)),
|
|
|
-
|
|
|
- textures: SyncMutex::new(vec![]),
|
|
|
dc_key: OsRng.gen(),
|
|
|
|
|
|
+ textures_pub,
|
|
|
+ textures_sub,
|
|
|
+ textures: Arc::new(SyncMutex::new(vec![])),
|
|
|
+ _load_handles: SyncMutex::new([const { None }; 4]),
|
|
|
+
|
|
|
rect,
|
|
|
uv,
|
|
|
z_index,
|
|
|
priority,
|
|
|
path,
|
|
|
- len,
|
|
|
+ vid_len,
|
|
|
|
|
|
parent_rect: SyncMutex::new(None),
|
|
|
});
|
|
|
@@ -105,37 +120,68 @@ impl Video {
|
|
|
}
|
|
|
|
|
|
fn load_textures(&self) {
|
|
|
- let (sendr, recvr) = async_channel::bounded(1);
|
|
|
- let len = self.len.get();
|
|
|
+ let vid_len = self.vid_len.get() as usize;
|
|
|
let path_fmt = self.path.get();
|
|
|
- let render_api = self.render_api.clone();
|
|
|
- let stop_load = self.stop_load.clone();
|
|
|
- let handle = std::thread::spawn(move || {
|
|
|
- let mut textures = Vec::with_capacity(len as usize);
|
|
|
- let instant = std::time::Instant::now();
|
|
|
- for i in 0..len {
|
|
|
- // Stop loading instantly
|
|
|
- if stop_load.load(Ordering::Relaxed) {
|
|
|
- return
|
|
|
- }
|
|
|
- t!("i = {i}");
|
|
|
- let path = path_fmt.replace("{frame}", &format!("{i:#03}"));
|
|
|
- let texture = Self::load_texture(path, &render_api);
|
|
|
- textures.push(texture);
|
|
|
- }
|
|
|
- t!("elapsed = {:?}", instant.elapsed());
|
|
|
- sendr.send_blocking(textures).unwrap();
|
|
|
- });
|
|
|
|
|
|
- // Temp here
|
|
|
- let Ok(textures) = recvr.recv_blocking() else {
|
|
|
- let node_ref = &self.node.upgrade().unwrap();
|
|
|
- t!("loading textures was stopped {node_ref:?}");
|
|
|
- return
|
|
|
- };
|
|
|
- assert!(handle.is_finished());
|
|
|
+ // Starts N threads
|
|
|
+ // batch idxs across threads
|
|
|
+ // 0 1 2 3
|
|
|
+ // 4 5 6 7
|
|
|
+ // ...
|
|
|
+ // load_texture:
|
|
|
+ // read image
|
|
|
+ // create texture
|
|
|
+ // set texture slot
|
|
|
+ // broadcast (idx, texture)
|
|
|
+ //
|
|
|
+ // draw_call:
|
|
|
+ // create broadcast sub
|
|
|
+ // load as many slots from mutex, then release
|
|
|
+ // start task:
|
|
|
+ // loop:
|
|
|
+ // listen to broadcast
|
|
|
+ // create draw_call
|
|
|
+ // send to gfx
|
|
|
+
|
|
|
+ {
|
|
|
+ let mut textures = self.textures.lock();
|
|
|
+ *textures = vec![None; vid_len];
|
|
|
+ self.textures_pub.clone().set_capacity(vid_len);
|
|
|
+ }
|
|
|
|
|
|
- *self.textures.lock() = textures;
|
|
|
+ let mut handles = [const { None }; 4];
|
|
|
+ for thread_idx in 0..N_LOADERS {
|
|
|
+ let path_fmt = path_fmt.clone();
|
|
|
+ let render_api = self.render_api.clone();
|
|
|
+ let stop_load = self.stop_load.clone();
|
|
|
+ let textures = self.textures.clone();
|
|
|
+ let textures_pub = self.textures_pub.clone();
|
|
|
+
|
|
|
+ let handle = std::thread::spawn(move || {
|
|
|
+ let mut frame_idx = thread_idx;
|
|
|
+ while frame_idx < vid_len {
|
|
|
+ // Stop loading instantly
|
|
|
+ if stop_load.load(Ordering::Relaxed) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ t!("frame_idx = {frame_idx} [thread={thread_idx}]");
|
|
|
+ let path = path_fmt.replace("{frame}", &format!("{frame_idx:#03}"));
|
|
|
+ let texture = Self::load_texture(path, &render_api);
|
|
|
+ // Make editing textures array and broadcasting an atomic op
|
|
|
+ {
|
|
|
+ let mut textures = textures.lock();
|
|
|
+ // set texture slot
|
|
|
+ textures[frame_idx] = Some(texture.clone());
|
|
|
+ // broadcast
|
|
|
+ textures_pub.try_broadcast((frame_idx, texture)).unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ frame_idx += N_LOADERS;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ handles[thread_idx] = Some(handle);
|
|
|
+ }
|
|
|
+ *self._load_handles.lock() = handles;
|
|
|
}
|
|
|
|
|
|
fn load_texture(path: String, render_api: &RenderApi) -> ManagedTexturePtr {
|
|
|
@@ -198,14 +244,67 @@ impl Video {
|
|
|
self.uv.eval(atom, &rect).ok()?;
|
|
|
|
|
|
let mesh = self.regen_mesh();
|
|
|
- let textures = self.textures.lock().clone();
|
|
|
- assert!(!textures.is_empty());
|
|
|
+ // Begin subscribing before we clone Mutex, but actually
|
|
|
+ // we need the length of the textures stored, so lets just hold it,
|
|
|
+ // do the clones THEN release.
|
|
|
+ let (textures, tsubs) = {
|
|
|
+ let textures = self.textures.lock();
|
|
|
+ // Possibly triggered by race condition.
|
|
|
+ // Check it anyway since generally should work.
|
|
|
+ assert_eq!(textures.len(), self.vid_len.get() as usize);
|
|
|
+ let tsubs = vec![self.textures_sub.clone(); textures.len()];
|
|
|
+ (textures.clone(), tsubs)
|
|
|
+ };
|
|
|
+ assert_eq!(textures.len(), tsubs.len());
|
|
|
+
|
|
|
+ // Only used in this function so fine to hold the entire time
|
|
|
+ let mut load_tasks = self.load_tasks.lock();
|
|
|
+ load_tasks.clear();
|
|
|
+
|
|
|
+ let (send_frames, recv_frames) = async_channel::bounded(textures.len());
|
|
|
|
|
|
let mut frames = Vec::with_capacity(textures.len());
|
|
|
- for texture in textures {
|
|
|
+ for (texture_idx, (mut texture, mut tsub)) in
|
|
|
+ textures.into_iter().zip(tsubs.into_iter()).enumerate()
|
|
|
+ {
|
|
|
+ let vertex_buffer = mesh.vertex_buffer.clone();
|
|
|
+ let index_buffer = mesh.index_buffer.clone();
|
|
|
+
|
|
|
+ let Some(texture) = texture.take() else {
|
|
|
+ frames.push(None);
|
|
|
+
|
|
|
+ let send_frames = send_frames.clone();
|
|
|
+ let task = self.ex.spawn(async move {
|
|
|
+ while let Ok((frame_idx, texture)) = tsub.recv().await {
|
|
|
+ if frame_idx != texture_idx {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ let mesh = DrawMesh {
|
|
|
+ vertex_buffer,
|
|
|
+ index_buffer,
|
|
|
+ texture: Some(texture),
|
|
|
+ num_elements: mesh.num_elements,
|
|
|
+ };
|
|
|
+ let dc = DrawCall {
|
|
|
+ instrs: vec![DrawInstruction::Draw(mesh)],
|
|
|
+ dcs: vec![],
|
|
|
+ z_index: 0,
|
|
|
+ debug_str: "video",
|
|
|
+ };
|
|
|
+
|
|
|
+ // send here
|
|
|
+ send_frames.send((frame_idx, Frame::new(40, dc)));
|
|
|
+ break
|
|
|
+ }
|
|
|
+ });
|
|
|
+ load_tasks.push(task);
|
|
|
+
|
|
|
+ continue
|
|
|
+ };
|
|
|
let mesh = DrawMesh {
|
|
|
- vertex_buffer: mesh.vertex_buffer.clone(),
|
|
|
- index_buffer: mesh.index_buffer.clone(),
|
|
|
+ vertex_buffer,
|
|
|
+ index_buffer,
|
|
|
texture: Some(texture),
|
|
|
num_elements: mesh.num_elements,
|
|
|
};
|
|
|
@@ -215,16 +314,17 @@ impl Video {
|
|
|
z_index: 0,
|
|
|
debug_str: "video",
|
|
|
};
|
|
|
- frames.push(SequenceAnimationFrame::new(40, dc));
|
|
|
+ frames.push(Some(Frame::new(40, dc)));
|
|
|
}
|
|
|
- let anim = SequenceAnimation::new(false, frames);
|
|
|
+ let anim = SeqAnim::new(false, frames, recv_frames);
|
|
|
|
|
|
Some(DrawUpdate {
|
|
|
key: self.dc_key,
|
|
|
draw_calls: vec![(
|
|
|
self.dc_key,
|
|
|
DrawCall::new(
|
|
|
- vec![DrawInstruction::Move(rect.pos()), DrawInstruction::Animation(anim)],
|
|
|
+ //vec![DrawInstruction::Move(rect.pos()), DrawInstruction::Animation(anim)],
|
|
|
+ vec![],
|
|
|
vec![],
|
|
|
self.z_index.get(),
|
|
|
"vid",
|