Просмотр исходного кода

app: working streaming video load which preserves state on UI changes/updates

darkfi 11 месяцев назад
Родитель
Сommit
1450bbb38e
4 измененных файлов с 226 добавлено и 202 удалено
  1. 6 0
      bin/app/src/error.rs
  2. 26 153
      bin/app/src/gfx/anim.rs
  3. 152 15
      bin/app/src/gfx/mod.rs
  4. 42 34
      bin/app/src/ui/video.rs

+ 6 - 0
bin/app/src/error.rs

@@ -133,6 +133,12 @@ pub enum Error {
 
     #[error("Unknown buffer ID")]
     GfxUnknownBufferID = 44,
+
+    #[error("Duplicate anim ID")]
+    GfxDuplicateAnimID = 45,
+
+    #[error("Unknown anim ID")]
+    GfxUnknownAnimID = 46,
 }
 
 impl From<sled::Error> for Error {

+ 26 - 153
bin/app/src/gfx/anim.rs

@@ -35,78 +35,6 @@ use super::{BufferId, DrawCall, GfxDrawCall, TextureId};
 
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "gfx::anim", $($arg)*); } }
 
-#[derive(Debug, Clone)]
-pub struct SeqAnim {
-    oneshot: bool,
-    frames: Vec<Option<Frame>>,
-    recv_frames: async_channel::Receiver<(usize, Frame)>,
-    state: State,
-}
-
-impl SeqAnim {
-    pub fn new(
-        oneshot: bool,
-        frames: Vec<Option<Frame>>,
-        recv_frames: async_channel::Receiver<(usize, Frame)>,
-        state: State,
-    ) -> Self {
-        Self { oneshot, frames, recv_frames, state }
-    }
-
-    pub(super) fn compile(
-        mut self: Self,
-        textures: &HashMap<TextureId, miniquad::TextureId>,
-        buffers: &HashMap<BufferId, miniquad::BufferId>,
-    ) -> GfxSeqAnim {
-        let mut frames = Vec::with_capacity(self.frames.len());
-        for frame in self.frames {
-            let Some(frame) = frame else {
-                frames.push(None);
-                continue
-            };
-            let duration = std::time::Duration::from_millis(frame.duration as u64);
-            let dc = frame.dc.compile(textures, buffers, 0).unwrap();
-            frames.push(Some(GfxFrame { duration, dc }));
-        }
-        GfxSeqAnim::new(self.oneshot, frames, self.recv_frames, self.state)
-    }
-}
-
-impl Encodable for SeqAnim {
-    fn encode<S: Write>(&self, s: &mut S) -> std::result::Result<usize, std::io::Error> {
-        let mut len = 0;
-        len += self.oneshot.encode(s)?;
-        // Write frames array
-        /*
-        len += VarInt(self.frames.len() as u64).encode(s)?;
-        for frame in &self.frames {
-            let frame = frame.read();
-            frame.encode(s)?;
-        }
-        */
-        Ok(len)
-    }
-}
-#[async_trait]
-impl AsyncEncodable for SeqAnim {
-    async fn encode_async<W: AsyncWrite + Unpin + Send>(
-        &self,
-        w: &mut W,
-    ) -> std::io::Result<usize> {
-        let mut len = 0;
-        len += self.oneshot.encode_async(w).await?;
-        // Write frames array
-        /*
-        len += VarInt(self.frames.len() as u64).encode_async(w).await?;
-        for frame in &self.frames {
-            let frame = frame.read().clone();
-            frame.encode_async(w).await?;
-        }
-        */
-        Ok(len)
-    }
-}
-
 #[derive(Debug, Clone)]
 pub struct Frame {
     /// Duration of this frame in ms
@@ -120,113 +48,58 @@ impl Frame {
     }
 }
 
-/// We have to implement this manually due to macro autism.
-/// Since it contains DrawCall that contains Instruction that can contain this.
-impl Encodable for Frame {
-    fn encode<S: Write>(&self, s: &mut S) -> std::result::Result<usize, std::io::Error> {
-        let mut len = 0;
-        len += self.duration.encode(s)?;
-        len += self.dc.encode(s)?;
-        Ok(len)
-    }
-}
-#[async_trait]
-impl AsyncEncodable for Frame {
-    async fn encode_async<W: AsyncWrite + Unpin + Send>(
-        &self,
-        w: &mut W,
-    ) -> std::io::Result<usize> {
-        let mut len = 0;
-        len += self.duration.encode_async(w).await?;
-        len += self.dc.encode_async(w).await?;
-        Ok(len)
-    }
-}
-
-#[derive(Debug)]
-struct InternalState {
-    /// Timer between frames
-    timer: std::time::Instant,
-    current_idx: usize,
-}
-
-type InternalStatePtr = Arc<RefCell<InternalState>>;
-
-#[derive(Debug, Clone)]
-pub struct State(InternalStatePtr);
-
-impl State {
-    pub fn new() -> Self {
-        Self(Arc::new(RefCell::new(InternalState {
-            timer: std::time::Instant::now(),
-            current_idx: 0,
-        })))
-    }
-}
-
-unsafe impl Send for State {}
-unsafe impl Sync for State {}
-
 #[derive(Debug, Clone)]
 pub(super) struct GfxSeqAnim {
     oneshot: bool,
     frames: Vec<Option<GfxFrame>>,
-    /// Stream frames in
-    recv_frames: async_channel::Receiver<(usize, Frame)>,
-    state: State,
+    /// Timer between frames
+    timer: std::time::Instant,
+    current_idx: usize,
 }
 
 impl GfxSeqAnim {
-    fn new(
-        oneshot: bool,
-        frames: Vec<Option<GfxFrame>>,
-        recv_frames: async_channel::Receiver<(usize, Frame)>,
-        state: State,
-    ) -> Self {
-        Self { oneshot, frames, recv_frames, state }
+    pub fn new(frames_len: usize, oneshot: bool) -> Self {
+        let frames = vec![None; frames_len];
+        Self { oneshot, frames, timer: std::time::Instant::now(), current_idx: 0 }
     }
 
-    pub fn tick(
+    pub fn set(
         &mut self,
+        frame_idx: usize,
+        frame: Frame,
         textures: &HashMap<TextureId, miniquad::TextureId>,
         buffers: &HashMap<BufferId, miniquad::BufferId>,
-    ) -> Option<GfxDrawCall> {
-        t!("tick");
-        while let Ok((frame_idx, frame)) = self.recv_frames.try_recv() {
-            let duration = std::time::Duration::from_millis(frame.duration as u64);
-            let dc = frame.dc.compile(textures, buffers, 0).unwrap();
-            self.frames[frame_idx] = Some(GfxFrame { duration, dc });
-            t!("got frame {frame_idx}");
-            for i in 0..self.frames.len() {
-                if self.frames[i].is_none() {
-                    t!("frame {i} is none");
-                }
-            }
-        }
-
-        let mut state = self.state.0.borrow_mut();
+    ) {
+        assert!(frame_idx < self.frames.len());
+        let duration = std::time::Duration::from_millis(frame.duration as u64);
+        let dc = frame.dc.compile(textures, buffers, 0).unwrap();
+        self.frames[frame_idx] = Some(GfxFrame { duration, dc });
+        //t!("got frame {frame_idx}");
+    }
 
-        let elapsed = state.timer.elapsed();
-        assert!(state.current_idx < self.frames.len());
-        let frame = &self.frames[state.current_idx];
+    pub fn tick(&mut self) -> Option<GfxDrawCall> {
+        //t!("tick");
+        let elapsed = self.timer.elapsed();
+        assert!(self.current_idx < self.frames.len());
+        let frame = &self.frames[self.current_idx];
         let Some(frame) = frame else {
-            assert_eq!(state.current_idx, 0);
+            assert_eq!(self.current_idx, 0);
             return None
         };
 
         let curr_duration = frame.duration;
         if elapsed >= curr_duration {
-            let next_idx = (state.current_idx + 1) % self.frames.len();
+            let next_idx = (self.current_idx + 1) % self.frames.len();
             // Only advance when the next frame is Some
             // Otherwise stay on the same frame
             if self.frames[next_idx].is_some() {
-                state.current_idx = next_idx;
+                self.current_idx = next_idx;
                 // Reset the timer now we changed frame
-                state.timer = std::time::Instant::now();
+                self.timer = std::time::Instant::now();
             }
         }
 
-        let curr_frame = self.frames[state.current_idx].clone().unwrap();
+        let curr_frame = self.frames[self.current_idx].clone().unwrap();
         Some(curr_frame.dc)
     }
 }

+ 152 - 15
bin/app/src/gfx/mod.rs

@@ -43,7 +43,7 @@ use std::{
 };
 
 pub mod anim;
-use anim::{GfxSeqAnim, SeqAnim};
+use anim::{Frame as AnimFrame, GfxSeqAnim};
 mod favico;
 mod linalg;
 pub use linalg::{Dimension, Point, Rectangle};
@@ -103,14 +103,15 @@ impl Vertex {
 
 pub type TextureId = u32;
 pub type BufferId = u32;
+pub type AnimId = u32;
 
 static NEXT_BUFFER_ID: AtomicU32 = AtomicU32::new(0);
 static NEXT_TEXTURE_ID: AtomicU32 = AtomicU32::new(0);
+static NEXT_ANIM_ID: AtomicU32 = AtomicU32::new(0);
 
 pub type ManagedTexturePtr = Arc<ManagedTexture>;
 
 /// Auto-deletes texture on drop
-#[derive(Clone)]
 pub struct ManagedTexture {
     id: TextureId,
     epoch: u32,
@@ -133,7 +134,6 @@ impl std::fmt::Debug for ManagedTexture {
 pub type ManagedBufferPtr = Arc<ManagedBuffer>;
 
 /// Auto-deletes buffer on drop
-#[derive(Clone)]
 pub struct ManagedBuffer {
     id: BufferId,
     epoch: u32,
@@ -154,6 +154,35 @@ impl std::fmt::Debug for ManagedBuffer {
     }
 }
 
+pub type ManagedSeqAnimPtr = Arc<ManagedSeqAnim>;
+
+pub struct ManagedSeqAnim {
+    frames_len: usize,
+    pub id: AnimId,
+    epoch: u32,
+    render_api: RenderApi,
+    tag: DebugTag,
+}
+
+impl ManagedSeqAnim {
+    pub fn update(&self, frame_idx: usize, frame: AnimFrame) {
+        assert!(frame_idx < self.frames_len);
+        self.render_api.update_unmanaged_anim(self.id, frame_idx, frame, self.epoch, self.tag);
+    }
+}
+
+impl Drop for ManagedSeqAnim {
+    fn drop(&mut self) {
+        self.render_api.delete_unmanaged_anim(self.id, self.epoch, self.tag);
+    }
+}
+
+impl std::fmt::Debug for ManagedSeqAnim {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("ManagedSeqAnim").field("id", &self.id).finish()
+    }
+}
+
 pub type EpochIndex = u32;
 
 #[derive(Clone)]
@@ -259,6 +288,42 @@ impl RenderApi {
         self.send_with_epoch(method, epoch);
     }
 
+    fn new_unmanaged_anim(
+        &self,
+        frames_len: usize,
+        oneshot: bool,
+        tag: DebugTag,
+    ) -> (AnimId, EpochIndex) {
+        let gfx_anim_id = NEXT_ANIM_ID.fetch_add(1, Ordering::Relaxed);
+
+        let method = GraphicsMethod::NewSeqAnim { id: gfx_anim_id, frames_len, oneshot, tag };
+        let epoch = self.send(method);
+
+        (gfx_anim_id, epoch)
+    }
+
+    pub fn new_anim(&self, frames_len: usize, oneshot: bool, tag: DebugTag) -> ManagedSeqAnimPtr {
+        let (id, epoch) = self.new_unmanaged_anim(frames_len, oneshot, tag);
+        Arc::new(ManagedSeqAnim { frames_len, id, epoch, render_api: self.clone(), tag })
+    }
+
+    pub fn update_unmanaged_anim(
+        &self,
+        anim: AnimId,
+        frame_idx: usize,
+        frame: AnimFrame,
+        epoch: EpochIndex,
+        tag: DebugTag,
+    ) {
+        let method = GraphicsMethod::UpdateSeqAnim { id: anim, frame_idx, frame, tag };
+        self.send_with_epoch(method, epoch);
+    }
+
+    fn delete_unmanaged_anim(&self, anim: AnimId, epoch: EpochIndex, tag: DebugTag) {
+        let method = GraphicsMethod::DeleteSeqAnim((anim, tag));
+        self.send_with_epoch(method, epoch);
+    }
+
     pub fn replace_draw_calls(
         &self,
         batch_id: BatchGuardId,
@@ -400,7 +465,7 @@ pub enum DrawInstruction {
     SetPos(Point),
     ApplyView(Rectangle),
     Draw(DrawMesh),
-    Animation(SeqAnim),
+    Animation(AnimId),
     EnableDebug,
 }
 
@@ -419,9 +484,7 @@ impl DrawInstruction {
             Self::Draw(mesh) => {
                 GfxDrawInstruction::Draw(mesh.compile(textures, buffers, debug_str)?)
             }
-            Self::Animation(anim) => {
-                GfxDrawInstruction::Animation(RefCell::new(anim.compile(textures, buffers)))
-            }
+            Self::Animation(anim) => GfxDrawInstruction::Animation(anim),
             Self::EnableDebug => GfxDrawInstruction::EnableDebug,
         };
         Some(instr)
@@ -482,7 +545,7 @@ enum GfxDrawInstruction {
     SetPos(Point),
     ApplyView(Rectangle),
     Draw(GfxDrawMesh),
-    Animation(RefCell<GfxSeqAnim>),
+    Animation(AnimId),
     EnableDebug,
 }
 
@@ -504,9 +567,7 @@ struct RenderContext<'a> {
     view: Rectangle,
     cursor: Point,
 
-    // Temp
-    textures: &'a HashMap<TextureId, miniquad::TextureId>,
-    buffers: &'a HashMap<BufferId, miniquad::BufferId>,
+    anims: &'a mut HashMap<AnimId, GfxSeqAnim>,
 }
 
 impl<'a> RenderContext<'a> {
@@ -639,8 +700,9 @@ impl<'a> RenderContext<'a> {
                     self.ctx.apply_bindings(&bindings);
                     self.ctx.draw(0, mesh.num_elements, 1);
                 }
-                GfxDrawInstruction::Animation(anim) => {
-                    if let Some(dc) = anim.borrow_mut().tick(self.textures, self.buffers) {
+                GfxDrawInstruction::Animation(anim_id) => {
+                    let anim = self.anims.get_mut(&anim_id).unwrap();
+                    if let Some(dc) = anim.tick() {
                         self.draw_call(&dc, indent + 1, is_debug);
                     }
                 }
@@ -692,6 +754,9 @@ pub enum GraphicsMethod {
     NewVertexBuffer((Vec<Vertex>, BufferId, DebugTag)),
     NewIndexBuffer((Vec<u16>, BufferId, DebugTag)),
     DeleteBuffer((BufferId, DebugTag, u8)),
+    NewSeqAnim { id: AnimId, frames_len: usize, oneshot: bool, tag: DebugTag },
+    UpdateSeqAnim { id: AnimId, frame_idx: usize, frame: AnimFrame, tag: DebugTag },
+    DeleteSeqAnim((AnimId, DebugTag)),
     ReplaceGfxDrawCalls { batch_id: BatchGuardId, timest: Timestamp, dcs: Vec<(DcId, DrawCall)> },
     StartBatch((BatchGuardId, Option<&'static str>)),
     EndBatch(BatchGuardId),
@@ -705,6 +770,9 @@ impl std::fmt::Debug for GraphicsMethod {
             Self::NewVertexBuffer(_) => write!(f, "NewVertexBuffer"),
             Self::NewIndexBuffer(_) => write!(f, "NewIndexBuffer"),
             Self::DeleteBuffer(_) => write!(f, "DeleteBuffer"),
+            Self::NewSeqAnim { .. } => write!(f, "NewSeqAnim"),
+            Self::UpdateSeqAnim { .. } => write!(f, "UpdateSeqAnim"),
+            Self::DeleteSeqAnim(_) => write!(f, "DeleteSeqAnim"),
             Self::ReplaceGfxDrawCalls { batch_id: bid, timest: _, dcs: _ } => {
                 write!(f, "ReplaceGfxDrawCalls({bid})")
             }
@@ -848,6 +916,7 @@ struct Stage {
 
     textures: HashMap<TextureId, miniquad::TextureId>,
     buffers: HashMap<BufferId, miniquad::BufferId>,
+    anims: HashMap<AnimId, GfxSeqAnim>,
 
     epoch: EpochIndex,
     method_queue: Arc<SyncMutex<Vec<(EpochIndex, GraphicsMethod)>>>,
@@ -945,6 +1014,7 @@ impl Stage {
 
             textures: HashMap::new(),
             buffers: HashMap::new(),
+            anims: HashMap::new(),
 
             epoch,
             method_queue,
@@ -969,6 +1039,13 @@ impl Stage {
                 self.method_new_index_buffer(indices, *gbuff_id)
             }
             GraphicsMethod::DeleteBuffer((gbuff_id, _, _)) => self.method_delete_buffer(*gbuff_id),
+            GraphicsMethod::NewSeqAnim { id, frames_len, oneshot, tag: _ } => {
+                self.method_new_anim(*id, *frames_len, *oneshot)
+            }
+            GraphicsMethod::UpdateSeqAnim { id, frame_idx, frame, tag: _ } => {
+                self.method_update_anim(*id, *frame_idx, frame.clone())
+            }
+            GraphicsMethod::DeleteSeqAnim((ganim_id, _)) => self.method_delete_anim(*ganim_id),
             GraphicsMethod::ReplaceGfxDrawCalls { batch_id, timest, dcs } => {
                 //let debug_strs: Vec<_> = dcs.iter().map(|(_, dc)| dc.debug_str).collect();
                 //t!("Commit dc to {batch_id}: {debug_strs:?}");
@@ -1134,6 +1211,47 @@ impl Stage {
         }
         Ok(())
     }
+    fn method_new_anim(
+        &mut self,
+        gfx_anim_id: AnimId,
+        frames_len: usize,
+        oneshot: bool,
+    ) -> Result<()> {
+        if DEBUG_GFXAPI {
+            debug!(target: "gfx", "Invoked method: new_anim({gfx_anim_id}, {frames_len}, {oneshot})");
+        }
+        if let Some(_) = self.anims.insert(gfx_anim_id, GfxSeqAnim::new(frames_len, oneshot)) {
+            //panic!("Duplicate index buffer ID={gfx_buffer_id} detected!");
+            return Err(Error::GfxDuplicateAnimID)
+        }
+        Ok(())
+    }
+    fn method_update_anim(
+        &mut self,
+        gfx_anim_id: AnimId,
+        frame_idx: usize,
+        frame: AnimFrame,
+    ) -> Result<()> {
+        let Some(anim) = self.anims.get_mut(&gfx_anim_id) else {
+            //.expect("couldn't find gfx_anim_id");
+            return Err(Error::GfxUnknownAnimID)
+        };
+        if DEBUG_GFXAPI {
+            debug!(target: "gfx", "Invoked method: update_anim({gfx_anim_id}[{frame_idx}] => {frame:?})");
+        }
+        anim.set(frame_idx, frame, &self.textures, &self.buffers);
+        Ok(())
+    }
+    fn method_delete_anim(&mut self, gfx_anim_id: AnimId) -> Result<()> {
+        let Some(anim) = self.anims.remove(&gfx_anim_id) else {
+            //.expect("couldn't find gfx_anim_id");
+            return Err(Error::GfxUnknownAnimID)
+        };
+        if DEBUG_GFXAPI {
+            debug!(target: "gfx", "Invoked method: delete_anim({} => {:?})", gfx_anim_id, anim);
+        }
+        Ok(())
+    }
     fn method_replace_draw_calls(
         &mut self,
         timest: Timestamp,
@@ -1195,6 +1313,15 @@ impl Stage {
             GraphicsMethod::DeleteBuffer((gbuff_id, tag, buftype)) => {
                 trax.del_buf(epoch, *gbuff_id, *tag, *buftype);
             }
+            GraphicsMethod::NewSeqAnim { id, frames_len, oneshot, tag } => {
+                //trax.put_idxs(epoch, idxs.clone(), *gbuff_id, *tag, 1);
+            }
+            GraphicsMethod::UpdateSeqAnim { .. } => {
+                //trax.put_idxs(epoch, idxs.clone(), *gbuff_id, *tag, 1);
+            }
+            GraphicsMethod::DeleteSeqAnim((ganim_id, tag)) => {
+                //trax.del_buf(epoch, *gbuff_id, *tag, *buftype);
+            }
             GraphicsMethod::ReplaceGfxDrawCalls { batch_id, timest, dcs } => {
                 trax.put_dcs(epoch, *batch_id, *timest, dcs);
             }
@@ -1279,6 +1406,17 @@ impl PruneMethodHeap {
                     self.del.push(method);
                 }
             }
+            GraphicsMethod::NewSeqAnim { id: _, frames_len, oneshot, tag } => {
+                //self.new_buf.insert(gbuff_id, method);
+            }
+            GraphicsMethod::UpdateSeqAnim { .. } => {
+                //self.new_buf.insert(gbuff_id, method);
+            }
+            GraphicsMethod::DeleteSeqAnim((ganim_id, _)) => {
+                //if self.new_buf.remove(&gbuff_id).is_none() {
+                //    self.del.push(method);
+                //}
+            }
             GraphicsMethod::ReplaceGfxDrawCalls { batch_id, timest, dcs } => {
                 self.method_replace_draw_calls(batch_id, timest, dcs)
             }
@@ -1435,8 +1573,7 @@ impl EventHandler for Stage {
             scale: 1.,
             view: Rectangle::from([0., 0., screen_w, screen_h]),
             cursor: Point::from([0., 0.]),
-            textures: &self.textures,
-            buffers: &self.buffers,
+            anims: &mut self.anims,
         };
         render_ctx.draw();
 

+ 42 - 34
bin/app/src/ui/video.rs

@@ -30,8 +30,8 @@ use std::{
 
 use crate::{
     gfx::{
-        anim::{Frame, SeqAnim, State as AnimState},
-        gfxtag, DrawCall, DrawInstruction, DrawMesh, ManagedTexturePtr, Rectangle, RenderApi,
+        anim::Frame, gfxtag, DrawCall, DrawInstruction, DrawMesh, ManagedSeqAnimPtr,
+        ManagedTexturePtr, Rectangle, RenderApi,
     },
     mesh::{MeshBuilder, MeshInfo, COLOR_WHITE},
     prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyRect, PropertyStr, PropertyUint32, Role},
@@ -48,6 +48,19 @@ macro_rules! t { ($($arg:tt)*) => { trace!(target: "ui::video", $($arg)*); } }
 
 pub type VideoPtr = Arc<Video>;
 
+#[derive(Clone)]
+struct StreamedVideoData {
+    textures: Vec<Option<ManagedTexturePtr>>,
+    anim: ManagedSeqAnimPtr,
+}
+
+impl StreamedVideoData {
+    fn new(len: usize, render_api: &RenderApi) -> Self {
+        let anim = render_api.new_anim(len, false, gfxtag!("video"));
+        Self { textures: vec![None; len], anim }
+    }
+}
+
 pub struct Video {
     node: SceneNodeWeak,
     render_api: RenderApi,
@@ -57,10 +70,9 @@ pub struct Video {
     stop_load: Arc<AtomicBool>,
     dc_key: u64,
 
-    anim_state: AnimState,
     textures_pub: async_broadcast::Sender<(usize, ManagedTexturePtr)>,
     textures_sub: async_broadcast::Receiver<(usize, ManagedTexturePtr)>,
-    textures: Arc<SyncMutex<Vec<Option<ManagedTexturePtr>>>>,
+    vid_data: Arc<SyncMutex<Option<StreamedVideoData>>>,
     // Do we need this?
     _load_handles: SyncMutex<[Option<std::thread::JoinHandle<()>>; N_LOADERS]>,
 
@@ -97,10 +109,9 @@ impl Video {
             stop_load: Arc::new(AtomicBool::new(false)),
             dc_key: OsRng.gen(),
 
-            anim_state: AnimState::new(),
             textures_pub,
             textures_sub,
-            textures: Arc::new(SyncMutex::new(vec![])),
+            vid_data: Arc::new(SyncMutex::new(None)),
             _load_handles: SyncMutex::new([const { None }; 4]),
 
             rect,
@@ -146,8 +157,8 @@ impl Video {
         //            send to gfx
 
         {
-            let mut textures = self.textures.lock();
-            *textures = vec![None; vid_len];
+            let mut vid_data = self.vid_data.lock();
+            *vid_data = Some(StreamedVideoData::new(vid_len, &self.render_api));
             self.textures_pub.clone().set_capacity(vid_len);
         }
 
@@ -156,7 +167,7 @@ impl Video {
             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 vid_data = self.vid_data.clone();
             let textures_pub = self.textures_pub.clone();
 
             let handle = std::thread::spawn(move || {
@@ -166,16 +177,15 @@ impl Video {
                     if stop_load.load(Ordering::Relaxed) {
                         return
                     }
-                    t!("frame_idx = {frame_idx} [thread={thread_idx}]");
+                    //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
-                        // panic here? hows that possible
-                        // happened on app close
-                        textures[frame_idx] = Some(texture.clone());
+                        let mut vid_data = vid_data.lock();
+                        // panic here on unwrap None when closing app
+                        let mut vid_data = vid_data.as_mut().unwrap();
+                        vid_data.textures[frame_idx] = Some(texture.clone());
                         // broadcast
                         textures_pub.try_broadcast((frame_idx, texture)).unwrap();
                     }
@@ -251,33 +261,29 @@ impl Video {
         // 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();
+        let (vid_data, tsubs) = {
+            let vid_data = self.vid_data.lock();
+            let vid_data = vid_data.clone().unwrap();
             // 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!(vid_data.textures.len(), self.vid_len.get() as usize);
+            let tsubs = vec![self.textures_sub.clone(); vid_data.textures.len()];
+            (vid_data, tsubs)
         };
-        assert_eq!(textures.len(), tsubs.len());
+        assert_eq!(vid_data.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_idx, (mut texture, mut tsub)) in
-            textures.into_iter().zip(tsubs.into_iter()).enumerate()
+            vid_data.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 anim = vid_data.anim.clone();
                 let task = self.ex.spawn(async move {
                     while let Ok((frame_idx, texture)) = tsub.recv().await {
                         if frame_idx != texture_idx {
@@ -297,8 +303,8 @@ impl Video {
                             debug_str: "video",
                         };
 
-                        // send here
-                        send_frames.send((frame_idx, Frame::new(40, dc)));
+                        //t!("sending {frame_idx}");
+                        anim.update(frame_idx, Frame::new(40, dc));
                         break
                     }
                 });
@@ -318,16 +324,18 @@ impl Video {
                 z_index: 0,
                 debug_str: "video",
             };
-            frames.push(Some(Frame::new(40, dc)));
+            vid_data.anim.update(texture_idx, Frame::new(40, dc));
         }
-        let anim = SeqAnim::new(false, frames, recv_frames, self.anim_state.clone());
 
         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(vid_data.anim.id),
+                    ],
                     vec![],
                     self.z_index.get(),
                     "vid",
@@ -362,7 +370,7 @@ impl UIObject for Video {
     fn stop(&self) {
         self.tasks.lock().clear();
         *self.parent_rect.lock() = None;
-        self.textures.lock().clear();
+        *self.vid_data.lock() = None;
     }
 
     async fn draw(