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

app/gfx: simplify and strengthen gfx engine. We now only buffer texture/buffer changes when screen is off, and when screen is switched on we just request a screen redraw. Additionally we now use the same time source which is the batch close time when merging batch changes. This theoretically should reduce screen tearing. We also keep track of dropped batches for debug and internal correctness checking, in general we have increased/tightened the internal logic.

darkfi 8 месяцев назад
Родитель
Сommit
8c507f6f06
4 измененных файлов с 211 добавлено и 159 удалено
  1. 208 156
      bin/app/src/gfx/mod.rs
  2. 1 1
      bin/app/src/ui/layer.rs
  3. 1 1
      bin/app/src/ui/vector_art/mod.rs
  4. 1 1
      bin/app/src/ui/win.rs

+ 208 - 156
bin/app/src/gfx/mod.rs

@@ -30,7 +30,7 @@ use miniquad::{
 };
 };
 use parking_lot::Mutex as SyncMutex;
 use parking_lot::Mutex as SyncMutex;
 use std::{
 use std::{
-    collections::HashMap,
+    collections::{HashMap, HashSet},
     fs::File,
     fs::File,
     io::Write,
     io::Write,
     path::PathBuf,
     path::PathBuf,
@@ -39,7 +39,7 @@ use std::{
         Arc,
         Arc,
     },
     },
 };
 };
-use tracing::debug;
+use tracing::{debug, span, Level};
 
 
 pub mod anim;
 pub mod anim;
 use anim::{Frame as AnimFrame, GfxSeqAnim};
 use anim::{Frame as AnimFrame, GfxSeqAnim};
@@ -53,6 +53,7 @@ use trax::get_trax;
 use crate::{
 use crate::{
     error::{Error, Result},
     error::{Error, Result},
     prop::{BatchGuardId, PropertyAtomicGuard},
     prop::{BatchGuardId, PropertyAtomicGuard},
+    util::unixtime,
     ExecutorPtr, GOD,
     ExecutorPtr, GOD,
 };
 };
 
 
@@ -71,6 +72,7 @@ pub use crate::gfxtag;
 
 
 pub type DebugTag = Option<&'static str>;
 pub type DebugTag = Option<&'static str>;
 
 
+macro_rules! d { ($($arg:tt)*) => { debug!(target: "gfx", $($arg)*); } }
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "gfx", $($arg)*); } }
 macro_rules! t { ($($arg:tt)*) => { trace!(target: "gfx", $($arg)*); } }
 macro_rules! e { ($($arg:tt)*) => { error!(target: "gfx", $($arg)*); } }
 macro_rules! e { ($($arg:tt)*) => { error!(target: "gfx", $($arg)*); } }
 
 
@@ -333,12 +335,13 @@ impl RenderApi {
         self.send(method);
         self.send(method);
     }
     }
 
 
-    fn start_batch(&self, batch_id: BatchGuardId, debug_str: Option<&'static str>) {
-        let method = GraphicsMethod::StartBatch((batch_id, debug_str));
+    fn start_batch(&self, batch_id: BatchGuardId, tag: DebugTag) {
+        let method = GraphicsMethod::StartBatch { batch_id, tag };
         self.send(method);
         self.send(method);
     }
     }
     fn end_batch(&self, batch_id: BatchGuardId) {
     fn end_batch(&self, batch_id: BatchGuardId) {
-        let method = GraphicsMethod::EndBatch(batch_id);
+        let timest = unixtime();
+        let method = GraphicsMethod::EndBatch { batch_id, timest };
         self.send(method);
         self.send(method);
     }
     }
 
 
@@ -390,13 +393,7 @@ impl DrawMesh {
         let gfx_texture_id = gfx_texture.id;
         let gfx_texture_id = gfx_texture.id;
 
 
         let Some(_mq_texture_id) = textures.get(&gfx_texture_id) else {
         let Some(_mq_texture_id) = textures.get(&gfx_texture_id) else {
-            error!(target: "gfx", "Serious error: missing texture ID={gfx_texture_id}, debug={debug_str}");
-            error!(target: "gfx", "Dumping textures:");
-            for (gfx_texture_id, texture_id) in textures {
-                error!(target: "gfx", "{gfx_texture_id} => {texture_id:?}");
-            }
-
-            panic!("Missing texture ID={gfx_texture_id}")
+            panic!("Missing texture ID={gfx_texture_id} debug={debug_str}")
         };
         };
 
 
         Some((gfx_texture, textures[&gfx_texture_id]))
         Some((gfx_texture, textures[&gfx_texture_id]))
@@ -408,13 +405,7 @@ impl DrawMesh {
         debug_str: &'static str,
         debug_str: &'static str,
     ) -> Option<miniquad::BufferId> {
     ) -> Option<miniquad::BufferId> {
         let Some(mq_buffer_id) = buffers.get(&gfx_buffer_id) else {
         let Some(mq_buffer_id) = buffers.get(&gfx_buffer_id) else {
-            error!(target: "gfx", "Serious error: missing buffer ID={gfx_buffer_id}, debug={debug_str}");
-            error!(target: "gfx", "Dumping buffers:");
-            for (gfx_buffer_id, buffer_id) in buffers {
-                error!(target: "gfx", "{gfx_buffer_id} => {buffer_id:?}");
-            }
-
-            panic!("Missing buffer ID={gfx_buffer_id}")
+            panic!("Missing buffer ID={gfx_buffer_id} debug={debug_str}")
         };
         };
         Some(*mq_buffer_id)
         Some(*mq_buffer_id)
     }
     }
@@ -758,8 +749,9 @@ pub enum GraphicsMethod {
     UpdateSeqAnim { id: AnimId, frame_idx: usize, frame: AnimFrame, tag: DebugTag },
     UpdateSeqAnim { id: AnimId, frame_idx: usize, frame: AnimFrame, tag: DebugTag },
     DeleteSeqAnim((AnimId, DebugTag)),
     DeleteSeqAnim((AnimId, DebugTag)),
     ReplaceGfxDrawCalls { batch_id: BatchGuardId, timest: Timestamp, dcs: Vec<(DcId, DrawCall)> },
     ReplaceGfxDrawCalls { batch_id: BatchGuardId, timest: Timestamp, dcs: Vec<(DcId, DrawCall)> },
-    StartBatch((BatchGuardId, Option<&'static str>)),
-    EndBatch(BatchGuardId),
+    StartBatch { batch_id: BatchGuardId, tag: DebugTag },
+    EndBatch { batch_id: BatchGuardId, timest: Timestamp },
+    Noop,
 }
 }
 
 
 impl std::fmt::Debug for GraphicsMethod {
 impl std::fmt::Debug for GraphicsMethod {
@@ -776,12 +768,19 @@ impl std::fmt::Debug for GraphicsMethod {
             Self::ReplaceGfxDrawCalls { batch_id: bid, timest: _, dcs: _ } => {
             Self::ReplaceGfxDrawCalls { batch_id: bid, timest: _, dcs: _ } => {
                 write!(f, "ReplaceGfxDrawCalls({bid})")
                 write!(f, "ReplaceGfxDrawCalls({bid})")
             }
             }
-            Self::StartBatch((bid, debug_str)) => write!(f, "StartBatch({bid}, {debug_str:?})"),
-            Self::EndBatch(bid) => write!(f, "EndBatch({bid})"),
+            Self::StartBatch { batch_id, tag } => write!(f, "StartBatch({batch_id}, {tag:?})"),
+            Self::EndBatch { batch_id, timest } => write!(f, "EndBatch({batch_id}, {timest})"),
+            Self::Noop => write!(f, "Noop"),
         }
         }
     }
     }
 }
 }
 
 
+impl Default for GraphicsMethod {
+    fn default() -> Self {
+        GraphicsMethod::Noop
+    }
+}
+
 struct EventChannel<T> {
 struct EventChannel<T> {
     sender: async_channel::Sender<T>,
     sender: async_channel::Sender<T>,
     recvr: async_channel::Receiver<T>,
     recvr: async_channel::Receiver<T>,
@@ -912,10 +911,13 @@ struct Stage {
     pipeline: Pipeline,
     pipeline: Pipeline,
     white_texture: miniquad::TextureId,
     white_texture: miniquad::TextureId,
     draw_calls: HashMap<DcId, GfxDrawCall>,
     draw_calls: HashMap<DcId, GfxDrawCall>,
-    batches: HashMap<BatchGuardId, Vec<GraphicsMethod>>,
+    pending_batches: HashMap<BatchGuardId, Vec<GraphicsMethod>>,
+    /// When dropping batches, we add to this set so that we keep track
+    /// of the internal state's correctness.
+    dropped_batches: Box<HashSet<BatchGuardId>>,
 
 
-    textures: HashMap<TextureId, miniquad::TextureId>,
-    buffers: HashMap<BufferId, miniquad::BufferId>,
+    textures: Box<HashMap<TextureId, miniquad::TextureId>>,
+    buffers: Box<HashMap<BufferId, miniquad::BufferId>>,
     anims: HashMap<AnimId, GfxSeqAnim>,
     anims: HashMap<AnimId, GfxSeqAnim>,
 
 
     epoch: EpochIndex,
     epoch: EpochIndex,
@@ -1006,7 +1008,7 @@ impl Stage {
         #[cfg(target_os = "android")]
         #[cfg(target_os = "android")]
         let libegl = egl::LibEgl::try_load().expect("Cant load LibEGL");
         let libegl = egl::LibEgl::try_load().expect("Cant load LibEGL");
 
 
-        Stage {
+        let mut self_ = Stage {
             ctx,
             ctx,
             #[cfg(target_os = "android")]
             #[cfg(target_os = "android")]
             libegl,
             libegl,
@@ -1016,10 +1018,11 @@ impl Stage {
                 0,
                 0,
                 GfxDrawCall { instrs: vec![], dcs: vec![], z_index: 0, timest: 0 },
                 GfxDrawCall { instrs: vec![], dcs: vec![], z_index: 0, timest: 0 },
             )]),
             )]),
-            batches: HashMap::new(),
+            pending_batches: HashMap::new(),
+            dropped_batches: Box::new(HashSet::new()),
 
 
-            textures: HashMap::new(),
-            buffers: HashMap::new(),
+            textures: Box::new(HashMap::new()),
+            buffers: Box::new(HashMap::new()),
             anims: HashMap::new(),
             anims: HashMap::new(),
 
 
             epoch,
             epoch,
@@ -1032,12 +1035,16 @@ impl Stage {
             ex,
             ex,
             #[cfg(target_os = "android")]
             #[cfg(target_os = "android")]
             refresh_task: None,
             refresh_task: None,
-        }
+        };
+        self_.pruner.textures = &*self_.textures as *const _;
+        self_.pruner.buffers = &*self_.buffers as *const _;
+        self_.pruner.dropped_batches = &mut *self_.dropped_batches as *mut _;
+        self_
     }
     }
 
 
     fn process_method(&mut self, mut method: GraphicsMethod) {
     fn process_method(&mut self, mut method: GraphicsMethod) {
-        //debug!(target: "gfx", "Received method: {:?}", method);
-        let res = match &mut method {
+        //d!("Received method: {method:?}");
+        match &mut method {
             GraphicsMethod::NewTexture((width, height, data, gtex_id, _)) => {
             GraphicsMethod::NewTexture((width, height, data, gtex_id, _)) => {
                 self.method_new_texture(*width, *height, data, *gtex_id)
                 self.method_new_texture(*width, *height, data, *gtex_id)
             }
             }
@@ -1056,53 +1063,51 @@ impl Stage {
                 self.method_update_anim(*id, *frame_idx, frame.clone())
                 self.method_update_anim(*id, *frame_idx, frame.clone())
             }
             }
             GraphicsMethod::DeleteSeqAnim((ganim_id, _)) => self.method_delete_anim(*ganim_id),
             GraphicsMethod::DeleteSeqAnim((ganim_id, _)) => self.method_delete_anim(*ganim_id),
-            GraphicsMethod::ReplaceGfxDrawCalls { batch_id, timest, dcs } => {
+            GraphicsMethod::ReplaceGfxDrawCalls { batch_id, .. } => {
                 //let debug_strs: Vec<_> = dcs.iter().map(|(_, dc)| dc.debug_str).collect();
                 //let debug_strs: Vec<_> = dcs.iter().map(|(_, dc)| dc.debug_str).collect();
                 //t!("Commit dc to {batch_id}: {debug_strs:?}");
                 //t!("Commit dc to {batch_id}: {debug_strs:?}");
-                let batch = self.batches.get_mut(batch_id).unwrap();
-                let dcs = std::mem::take(dcs);
-                batch.push(GraphicsMethod::ReplaceGfxDrawCalls {
-                    batch_id: *batch_id,
-                    timest: *timest,
-                    dcs,
-                });
+                if self.dropped_batches.contains(batch_id) {
+                    t!("Discarding ReplaceGfxDrawCalls from dropped {batch_id}");
+                    return
+                }
+                let Some(batch) = self.pending_batches.get_mut(batch_id) else {
+                    panic!("unknown batch {batch_id}")
+                };
+                let method = std::mem::take(&mut method);
+                batch.push(method);
                 if DEBUG_TRAX {
                 if DEBUG_TRAX {
                     get_trax().lock().put_stat(0);
                     get_trax().lock().put_stat(0);
                 }
                 }
-                Ok(())
             }
             }
-            GraphicsMethod::StartBatch((batch_id, _debug_str)) => {
-                //t!("Start batch {batch_id}: {debug_str:?}");
-                if !self.batches.insert(*batch_id, vec![]).is_none() {
-                    panic!("Batch {batch_id} already open!")
+            GraphicsMethod::StartBatch { batch_id, tag } => {
+                t!("Start batch {batch_id}: {tag:?}");
+                if !self.pending_batches.insert(*batch_id, vec![]).is_none() {
+                    panic!("batch {batch_id} already open!")
                 }
                 }
                 if DEBUG_TRAX {
                 if DEBUG_TRAX {
                     get_trax().lock().put_stat(0);
                     get_trax().lock().put_stat(0);
                 }
                 }
-                Ok(())
             }
             }
-            GraphicsMethod::EndBatch(batch_id) => {
-                //t!("End batch {batch_id}");
-                let batch = self.batches.remove(batch_id).unwrap();
+            GraphicsMethod::EndBatch { batch_id, timest } => {
+                if self.dropped_batches.remove(batch_id) {
+                    t!("End batch {batch_id} was dropped");
+                    return
+                }
+                t!("End batch {batch_id}");
+                let Some(batch) = self.pending_batches.remove(batch_id) else {
+                    panic!("unknown batch {batch_id}")
+                };
                 for mut method in batch {
                 for mut method in batch {
-                    let res = match &mut method {
-                        GraphicsMethod::ReplaceGfxDrawCalls { batch_id: _, timest, dcs } => {
+                    match &mut method {
+                        GraphicsMethod::ReplaceGfxDrawCalls { batch_id: _, timest: _, dcs } => {
                             let dcs = std::mem::take(dcs);
                             let dcs = std::mem::take(dcs);
                             self.method_replace_draw_calls(*timest, dcs)
                             self.method_replace_draw_calls(*timest, dcs)
                         }
                         }
                         _ => panic!("unexpected method in batch!"),
                         _ => panic!("unexpected method in batch!"),
-                    };
-                    if let Err(err) = res {
-                        e!("process_method(method={method:?}) failed with err: {err:?}");
-                        panic!("process_method failed!")
                     }
                     }
                 }
                 }
-                Ok(())
             }
             }
-        };
-        if let Err(err) = res {
-            e!("process_method(method={method:?}) failed with err: {err:?}");
-            panic!("process_method failed!")
+            GraphicsMethod::Noop => panic!("noop"),
         }
         }
     }
     }
 
 
@@ -1112,12 +1117,11 @@ impl Stage {
         height: u16,
         height: u16,
         data: &Vec<u8>,
         data: &Vec<u8>,
         gfx_texture_id: TextureId,
         gfx_texture_id: TextureId,
-    ) -> Result<()> {
+    ) {
         let texture = self.ctx.new_texture_from_rgba8(width, height, data);
         let texture = self.ctx.new_texture_from_rgba8(width, height, data);
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: new_texture({}, {}, ..., {}) -> {:?}",
-                   width, height, gfx_texture_id, texture);
-            //debug!(target: "gfx", "Invoked method: new_texture({}, {}, ..., {}) -> {:?}\n{}",
+            d!("Invoked method: new_texture({width}, {height}, ..., {gfx_texture_id}) -> {texture:?}");
+            //d!("Invoked method: new_texture({}, {}, ..., {}) -> {:?}\n{}",
             //       width, height, gfx_texture_id, texture,
             //       width, height, gfx_texture_id, texture,
             //       ansi_texture(width as usize, height as usize, &data));
             //       ansi_texture(width as usize, height as usize, &data));
         }
         }
@@ -1125,45 +1129,35 @@ impl Stage {
             if DEBUG_TRAX {
             if DEBUG_TRAX {
                 get_trax().lock().put_stat(2);
                 get_trax().lock().put_stat(2);
             }
             }
-            //panic!("Duplicate texture ID={gfx_texture_id} detected!");
-            return Err(Error::GfxDuplicateTextureID)
+            panic!("Duplicate texture ID={gfx_texture_id} detected!");
         }
         }
         if DEBUG_TRAX {
         if DEBUG_TRAX {
             get_trax().lock().put_stat(0);
             get_trax().lock().put_stat(0);
         }
         }
-        Ok(())
     }
     }
-    fn method_delete_texture(&mut self, gfx_texture_id: TextureId) -> Result<()> {
+    fn method_delete_texture(&mut self, gfx_texture_id: TextureId) {
         let Some(texture) = self.textures.remove(&gfx_texture_id) else {
         let Some(texture) = self.textures.remove(&gfx_texture_id) else {
             if DEBUG_TRAX {
             if DEBUG_TRAX {
                 get_trax().lock().put_stat(2);
                 get_trax().lock().put_stat(2);
             }
             }
-            //.expect("couldn't find gfx_texture_id");
-            return Err(Error::GfxUnknownTextureID)
+            panic!("unknown texture {gfx_texture_id}")
         };
         };
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: delete_texture({} => {:?})",
-                   gfx_texture_id, texture);
+            d!("Invoked method: delete_texture({gfx_texture_id} => {texture:?})");
         }
         }
         self.ctx.delete_texture(texture);
         self.ctx.delete_texture(texture);
         if DEBUG_TRAX {
         if DEBUG_TRAX {
             get_trax().lock().put_stat(0);
             get_trax().lock().put_stat(0);
         }
         }
-        Ok(())
     }
     }
-    fn method_new_vertex_buffer(
-        &mut self,
-        verts: &[Vertex],
-        gfx_buffer_id: BufferId,
-    ) -> Result<()> {
+    fn method_new_vertex_buffer(&mut self, verts: &[Vertex], gfx_buffer_id: BufferId) {
         let buffer = self.ctx.new_buffer(
         let buffer = self.ctx.new_buffer(
             BufferType::VertexBuffer,
             BufferType::VertexBuffer,
             BufferUsage::Immutable,
             BufferUsage::Immutable,
             BufferSource::slice(verts),
             BufferSource::slice(verts),
         );
         );
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: new_vertex_buffer(..., {}) -> {:?}",
-                   gfx_buffer_id, buffer);
+            d!("Invoked method: new_vertex_buffer(..., {gfx_buffer_id}) -> {buffer:?}");
             //debug!(target: "gfx", "Invoked method: new_vertex_buffer({:?}, {}) -> {:?}",
             //debug!(target: "gfx", "Invoked method: new_vertex_buffer({:?}, {}) -> {:?}",
             //       verts, gfx_buffer_id, buffer);
             //       verts, gfx_buffer_id, buffer);
         }
         }
@@ -1171,23 +1165,20 @@ impl Stage {
             if DEBUG_TRAX {
             if DEBUG_TRAX {
                 get_trax().lock().put_stat(2);
                 get_trax().lock().put_stat(2);
             }
             }
-            //panic!("Duplicate vertex buffer ID={gfx_buffer_id} detected!");
-            return Err(Error::GfxDuplicateBufferID)
+            panic!("Duplicate vertex buffer ID={gfx_buffer_id} detected!")
         }
         }
         if DEBUG_TRAX {
         if DEBUG_TRAX {
             get_trax().lock().put_stat(0);
             get_trax().lock().put_stat(0);
         }
         }
-        Ok(())
     }
     }
-    fn method_new_index_buffer(&mut self, indices: &[u16], gfx_buffer_id: BufferId) -> Result<()> {
+    fn method_new_index_buffer(&mut self, indices: &[u16], gfx_buffer_id: BufferId) {
         let buffer = self.ctx.new_buffer(
         let buffer = self.ctx.new_buffer(
             BufferType::IndexBuffer,
             BufferType::IndexBuffer,
             BufferUsage::Immutable,
             BufferUsage::Immutable,
             BufferSource::slice(&indices),
             BufferSource::slice(&indices),
         );
         );
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: new_index_buffer({}) -> {:?}",
-                   gfx_buffer_id, buffer);
+            d!("Invoked method: new_index_buffer({gfx_buffer_id}) -> {buffer:?}");
             //debug!(target: "gfx", "Invoked method: new_index_buffer({:?}, {}) -> {:?}",
             //debug!(target: "gfx", "Invoked method: new_index_buffer({:?}, {}) -> {:?}",
             //       indices, gfx_buffer_id, buffer);
             //       indices, gfx_buffer_id, buffer);
         }
         }
@@ -1195,88 +1186,62 @@ impl Stage {
             if DEBUG_TRAX {
             if DEBUG_TRAX {
                 get_trax().lock().put_stat(2);
                 get_trax().lock().put_stat(2);
             }
             }
-            //panic!("Duplicate index buffer ID={gfx_buffer_id} detected!");
-            return Err(Error::GfxDuplicateBufferID)
+            panic!("Duplicate index buffer ID={gfx_buffer_id} detected!")
         }
         }
         if DEBUG_TRAX {
         if DEBUG_TRAX {
             get_trax().lock().put_stat(0);
             get_trax().lock().put_stat(0);
         }
         }
-        Ok(())
     }
     }
-    fn method_delete_buffer(&mut self, gfx_buffer_id: BufferId) -> Result<()> {
+    fn method_delete_buffer(&mut self, gfx_buffer_id: BufferId) {
         let Some(buffer) = self.buffers.remove(&gfx_buffer_id) else {
         let Some(buffer) = self.buffers.remove(&gfx_buffer_id) else {
             if DEBUG_TRAX {
             if DEBUG_TRAX {
                 get_trax().lock().put_stat(2);
                 get_trax().lock().put_stat(2);
             }
             }
-            //.expect("couldn't find gfx_buffer_id");
-            return Err(Error::GfxUnknownBufferID)
+            panic!("unknown buffer {gfx_buffer_id}");
         };
         };
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: delete_buffer({} => {:?})",
-                   gfx_buffer_id, buffer);
+            d!("Invoked method: delete_buffer({gfx_buffer_id} => {buffer:?})");
         }
         }
         self.ctx.delete_buffer(buffer);
         self.ctx.delete_buffer(buffer);
         if DEBUG_TRAX {
         if DEBUG_TRAX {
             get_trax().lock().put_stat(0);
             get_trax().lock().put_stat(0);
         }
         }
-        Ok(())
     }
     }
-    fn method_new_anim(
-        &mut self,
-        gfx_anim_id: AnimId,
-        frames_len: usize,
-        oneshot: bool,
-    ) -> Result<()> {
+    fn method_new_anim(&mut self, gfx_anim_id: AnimId, frames_len: usize, oneshot: bool) {
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: new_anim({gfx_anim_id}, {frames_len}, {oneshot})");
+            d!("Invoked method: new_anim({gfx_anim_id}, {frames_len}, {oneshot})");
         }
         }
         if let Some(_) = self.anims.insert(gfx_anim_id, GfxSeqAnim::new(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)
+            panic!("Duplicate anim ID={gfx_anim_id} detected!");
         }
         }
-        Ok(())
     }
     }
-    fn method_update_anim(
-        &mut self,
-        gfx_anim_id: AnimId,
-        frame_idx: usize,
-        frame: AnimFrame,
-    ) -> Result<()> {
+    fn method_update_anim(&mut self, gfx_anim_id: AnimId, frame_idx: usize, frame: AnimFrame) {
         let Some(anim) = self.anims.get_mut(&gfx_anim_id) else {
         let Some(anim) = self.anims.get_mut(&gfx_anim_id) else {
-            //.expect("couldn't find gfx_anim_id");
-            return Err(Error::GfxUnknownAnimID)
+            panic!("couldn't find anim {gfx_anim_id}");
         };
         };
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: update_anim({gfx_anim_id}[{frame_idx}] => {frame:?})");
+            d!("Invoked method: update_anim({gfx_anim_id}[{frame_idx}] => {frame:?})");
         }
         }
         anim.set(frame_idx, frame, &self.textures, &self.buffers);
         anim.set(frame_idx, frame, &self.textures, &self.buffers);
-        Ok(())
     }
     }
-    fn method_delete_anim(&mut self, gfx_anim_id: AnimId) -> Result<()> {
+    fn method_delete_anim(&mut self, gfx_anim_id: AnimId) {
         let Some(anim) = self.anims.remove(&gfx_anim_id) else {
         let Some(anim) = self.anims.remove(&gfx_anim_id) else {
-            //.expect("couldn't find gfx_anim_id");
-            return Err(Error::GfxUnknownAnimID)
+            panic!("couldn't find anim {gfx_anim_id}");
         };
         };
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: delete_anim({} => {:?})", gfx_anim_id, anim);
+            d!("Invoked method: delete_anim({} => {:?})", gfx_anim_id, anim);
         }
         }
-        Ok(())
     }
     }
-    fn method_replace_draw_calls(
-        &mut self,
-        timest: Timestamp,
-        dcs: Vec<(DcId, DrawCall)>,
-    ) -> Result<()> {
+    fn method_replace_draw_calls(&mut self, timest: Timestamp, dcs: Vec<(DcId, DrawCall)>) {
         if DEBUG_GFXAPI {
         if DEBUG_GFXAPI {
-            debug!(target: "gfx", "Invoked method: replace_draw_calls({:?})", dcs);
+            d!("Invoked method: replace_draw_calls({:?})", dcs);
         }
         }
         for (key, val) in dcs {
         for (key, val) in dcs {
             let Some(val) = val.compile(&self.textures, &self.buffers, timest) else {
             let Some(val) = val.compile(&self.textures, &self.buffers, timest) else {
                 if DEBUG_TRAX {
                 if DEBUG_TRAX {
                     get_trax().lock().put_stat(3);
                     get_trax().lock().put_stat(3);
                 }
                 }
-                error!(target: "gfx", "fatal: replace_draw_calls({timest}, ...) failed with item ID={key}");
-                continue
+                panic!("fatal: replace_draw_calls({timest}, ...) failed with item ID={key}")
             };
             };
             //self.draw_calls.insert(key, val);
             //self.draw_calls.insert(key, val);
             match self.draw_calls.get_mut(&key) {
             match self.draw_calls.get_mut(&key) {
@@ -1288,7 +1253,7 @@ impl Stage {
                         }
                         }
                         *old_val = val;
                         *old_val = val;
                     } else {
                     } else {
-                        trace!(target: "gfx", "Rejected stale draw_call {key}: {val:?}");
+                        t!("Rejected stale draw_call {key}: {val:?}");
                         if DEBUG_TRAX {
                         if DEBUG_TRAX {
                             get_trax().lock().put_stat(2);
                             get_trax().lock().put_stat(2);
                         }
                         }
@@ -1302,7 +1267,6 @@ impl Stage {
                 }
                 }
             }
             }
         }
         }
-        Ok(())
     }
     }
 
 
     fn trax_method(&self, epoch: EpochIndex, method: &GraphicsMethod) {
     fn trax_method(&self, epoch: EpochIndex, method: &GraphicsMethod) {
@@ -1335,12 +1299,13 @@ impl Stage {
             GraphicsMethod::ReplaceGfxDrawCalls { batch_id, timest, dcs } => {
             GraphicsMethod::ReplaceGfxDrawCalls { batch_id, timest, dcs } => {
                 trax.put_dcs(epoch, *batch_id, *timest, dcs);
                 trax.put_dcs(epoch, *batch_id, *timest, dcs);
             }
             }
-            GraphicsMethod::StartBatch((batch_id, debug_str)) => {
-                trax.put_start_batch(epoch, *batch_id, *debug_str);
+            GraphicsMethod::StartBatch { batch_id, tag } => {
+                trax.put_start_batch(epoch, *batch_id, *tag);
             }
             }
-            GraphicsMethod::EndBatch(batch_id) => {
+            GraphicsMethod::EndBatch { batch_id, timest: _ } => {
                 trax.put_end_batch(epoch, *batch_id);
                 trax.put_end_batch(epoch, *batch_id);
             }
             }
+            GraphicsMethod::Noop => panic!("noop"),
         };
         };
     }
     }
 
 
@@ -1369,6 +1334,10 @@ struct PruneMethodHeap {
     dcs: HashMap<DcId, (BatchGuardId, Timestamp, DrawCall)>,
     dcs: HashMap<DcId, (BatchGuardId, Timestamp, DrawCall)>,
 
 
     epoch: EpochIndex,
     epoch: EpochIndex,
+
+    textures: *const HashMap<TextureId, miniquad::TextureId>,
+    buffers: *const HashMap<BufferId, miniquad::BufferId>,
+    dropped_batches: *mut HashSet<BatchGuardId>,
 }
 }
 
 
 impl PruneMethodHeap {
 impl PruneMethodHeap {
@@ -1379,6 +1348,9 @@ impl PruneMethodHeap {
             del: vec![],
             del: vec![],
             dcs: HashMap::new(),
             dcs: HashMap::new(),
             epoch,
             epoch,
+            textures: std::ptr::null(),
+            buffers: std::ptr::null(),
+            dropped_batches: std::ptr::null_mut(),
         }
         }
     }
     }
 
 
@@ -1395,25 +1367,52 @@ impl PruneMethodHeap {
         }
         }
     }
     }
 
 
-    fn process_method(&mut self, method: GraphicsMethod) {
-        match method.clone() {
+    fn process_method(&mut self, mut method: GraphicsMethod) {
+        match &method {
             GraphicsMethod::NewTexture((_, _, _, gtex_id, _)) => {
             GraphicsMethod::NewTexture((_, _, _, gtex_id, _)) => {
-                self.new_tex.insert(gtex_id, method);
+                if DEBUG_GFXAPI {
+                    t!("Prune method: new_texture(..., {gtex_id})");
+                }
+                self.new_tex.insert(*gtex_id, std::mem::take(&mut method));
             }
             }
             GraphicsMethod::DeleteTexture((gtex_id, _)) => {
             GraphicsMethod::DeleteTexture((gtex_id, _)) => {
+                if DEBUG_GFXAPI {
+                    t!("Prune method: delete_texture(..., {gtex_id})");
+                }
                 if self.new_tex.remove(&gtex_id).is_none() {
                 if self.new_tex.remove(&gtex_id).is_none() {
+                    if !self.textures().contains_key(&gtex_id) {
+                        panic!("delete_texture missing ID {gtex_id} in pruner")
+                    }
+                    let method = std::mem::take(&mut method);
                     self.del.push(method);
                     self.del.push(method);
+                } else if DEBUG_GFXAPI {
+                    t!("Discard ellided texture {gtex_id}");
                 }
                 }
             }
             }
             GraphicsMethod::NewVertexBuffer((_, gbuff_id, _)) => {
             GraphicsMethod::NewVertexBuffer((_, gbuff_id, _)) => {
-                self.new_buf.insert(gbuff_id, method);
+                if DEBUG_GFXAPI {
+                    t!("Prune method: new_vertex_buffer(..., {gbuff_id})");
+                }
+                self.new_buf.insert(*gbuff_id, std::mem::take(&mut method));
             }
             }
             GraphicsMethod::NewIndexBuffer((_, gbuff_id, _)) => {
             GraphicsMethod::NewIndexBuffer((_, gbuff_id, _)) => {
-                self.new_buf.insert(gbuff_id, method);
+                if DEBUG_GFXAPI {
+                    t!("Prune method: new_index_buffer(..., {gbuff_id})");
+                }
+                self.new_buf.insert(*gbuff_id, std::mem::take(&mut method));
             }
             }
             GraphicsMethod::DeleteBuffer((gbuff_id, _, _)) => {
             GraphicsMethod::DeleteBuffer((gbuff_id, _, _)) => {
+                if DEBUG_GFXAPI {
+                    t!("Prune method: delete_buffer(..., {gbuff_id})");
+                }
                 if self.new_buf.remove(&gbuff_id).is_none() {
                 if self.new_buf.remove(&gbuff_id).is_none() {
+                    if !self.buffers().contains_key(&gbuff_id) {
+                        panic!("delete_buffer missing ID {gbuff_id} in pruner")
+                    }
+                    let method = std::mem::take(&mut method);
                     self.del.push(method);
                     self.del.push(method);
+                } else if DEBUG_GFXAPI {
+                    t!("Discard ellided buffer {gbuff_id}");
                 }
                 }
             }
             }
             GraphicsMethod::NewSeqAnim { .. } => {
             GraphicsMethod::NewSeqAnim { .. } => {
@@ -1428,15 +1427,38 @@ impl PruneMethodHeap {
                 //}
                 //}
             }
             }
             GraphicsMethod::ReplaceGfxDrawCalls { batch_id, timest, dcs } => {
             GraphicsMethod::ReplaceGfxDrawCalls { batch_id, timest, dcs } => {
-                self.method_replace_draw_calls(batch_id, timest, dcs)
+                //self.method_replace_draw_calls(batch_id, timest, dcs)
             }
             }
             // Discard batches since we will apply everything all at once anyway
             // Discard batches since we will apply everything all at once anyway
             // once the screen is switched on.
             // once the screen is switched on.
-            GraphicsMethod::StartBatch(_) => {}
-            GraphicsMethod::EndBatch(_) => {}
+            GraphicsMethod::StartBatch { batch_id, tag } => {
+                t!("Pruner drop start batch {batch_id}");
+                if !self.dropped_batches().insert(*batch_id) {
+                    panic!("dropped batch {batch_id} already exits!");
+                }
+            }
+            GraphicsMethod::EndBatch { batch_id, timest: _ } => {
+                t!("Pruner drop end batch {batch_id}");
+                // Should have already been dropped previously
+                assert!(self.dropped_batches().contains(batch_id));
+            }
+            GraphicsMethod::Noop => panic!("noop"),
         }
         }
     }
     }
 
 
+    fn textures(&self) -> &HashMap<TextureId, miniquad::TextureId> {
+        assert!(!self.textures.is_null());
+        unsafe { &*self.textures }
+    }
+    fn buffers(&self) -> &HashMap<BufferId, miniquad::BufferId> {
+        assert!(!self.buffers.is_null());
+        unsafe { &*self.buffers }
+    }
+    fn dropped_batches(&mut self) -> &mut HashSet<BatchGuardId> {
+        assert!(!self.dropped_batches.is_null());
+        unsafe { &mut *self.dropped_batches }
+    }
+
     fn method_replace_draw_calls(
     fn method_replace_draw_calls(
         &mut self,
         &mut self,
         batch_id: BatchGuardId,
         batch_id: BatchGuardId,
@@ -1486,22 +1508,30 @@ impl EventHandler for Stage {
     fn update(&mut self) {
     fn update(&mut self) {
         // todo: trax is all messed up in this func
         // todo: trax is all messed up in this func
 
 
-        let methods = std::mem::take(&mut *self.method_queue.lock());
-
         if self.egl_ctx_is_disabled() {
         if self.egl_ctx_is_disabled() {
             #[cfg(target_os = "android")]
             #[cfg(target_os = "android")]
             {
             {
                 self.refresh_task = None;
                 self.refresh_task = None;
             }
             }
+            if !self.screen_was_off {
+                d!("Screen is switched off");
+            }
 
 
             // Immediately apply any pending batches when the screen is switched off
             // Immediately apply any pending batches when the screen is switched off
-            let batch_ids: Vec<_> = self.batches.keys().cloned().collect();
+            let batch_ids: Vec<_> = self.pending_batches.keys().cloned().collect();
+            if !batch_ids.is_empty() {
+                t!("Force closing pending batches: {batch_ids:?}");
+            }
             for batch_id in batch_ids {
             for batch_id in batch_ids {
-                self.process_method(GraphicsMethod::EndBatch(batch_id));
+                self.process_method(GraphicsMethod::EndBatch { batch_id, timest: unixtime() });
+                if !self.dropped_batches.insert(batch_id) {
+                    panic!("dropped batch {batch_id} already exits!");
+                }
             }
             }
-            self.batches.clear();
+            assert!(self.pending_batches.is_empty());
 
 
             // Screen is off so collect all methods into the pruner
             // Screen is off so collect all methods into the pruner
+            let methods = std::mem::take(&mut *self.method_queue.lock());
             self.pruner.drain(methods);
             self.pruner.drain(methods);
             self.screen_was_off = true;
             self.screen_was_off = true;
             return
             return
@@ -1519,13 +1549,18 @@ impl EventHandler for Stage {
         }
         }
 
 
         // We actually want to skip draining the prune queue the first time so
         // We actually want to skip draining the prune queue the first time so
-        // draw actually gets a chance to be called first.
-        // Otherwise we will just see a black screen for a sec or so.
+        // miniquad draw() actually gets a chance to be called first.
+        // Otherwise we will see a black screen for a sec or so while update() is running.
         if self.screen_was_off {
         if self.screen_was_off {
             self.screen_was_off = false;
             self.screen_was_off = false;
+            d!("Screen is switched on");
+            return
         } else {
         } else {
+            let span = span!(Level::TRACE, "pruner");
+            let _enter = span.enter();
+
             let methods = self.pruner.recv_all();
             let methods = self.pruner.recv_all();
-            assert!(methods.is_empty() || self.batches.is_empty());
+            assert!(methods.is_empty() || self.pending_batches.is_empty());
             // Process all cached methods by the pruner from while the screen was off.
             // Process all cached methods by the pruner from while the screen was off.
             for method in methods {
             for method in methods {
                 // Stale methods will be dropped by pruner, so they will not be caught by trax
                 // Stale methods will be dropped by pruner, so they will not be caught by trax
@@ -1536,21 +1571,38 @@ impl EventHandler for Stage {
                 // We discard batches here but process_method uses them so implement this
                 // We discard batches here but process_method uses them so implement this
                 // workaround.
                 // workaround.
                 match method {
                 match method {
-                    GraphicsMethod::ReplaceGfxDrawCalls { batch_id: _, timest, dcs } => {
-                        if let Err(err) = self.method_replace_draw_calls(timest, dcs) {
-                            e!("process_method for ReplaceGfxDrawCalls failed err: {err:?}");
-                            panic!("process_method failed!")
-                        }
+                    GraphicsMethod::NewTexture(_) |
+                    GraphicsMethod::DeleteTexture(_) |
+                    GraphicsMethod::NewVertexBuffer(_) |
+                    GraphicsMethod::NewIndexBuffer(_) |
+                    GraphicsMethod::DeleteBuffer(_) => self.process_method(method),
+
+                    GraphicsMethod::ReplaceGfxDrawCalls { .. } |
+                    GraphicsMethod::NewSeqAnim { .. } |
+                    GraphicsMethod::UpdateSeqAnim { .. } |
+                    GraphicsMethod::DeleteSeqAnim(_) |
+                    GraphicsMethod::StartBatch { .. } |
+                    GraphicsMethod::EndBatch { .. } => {
+                        panic!("unsupported pruned methods should be dropped!")
                     }
                     }
-                    _ => self.process_method(method),
+
+                    GraphicsMethod::Noop => panic!("noop"),
                 }
                 }
                 if DEBUG_TRAX {
                 if DEBUG_TRAX {
                     get_trax().lock().flush();
                     get_trax().lock().flush();
                 }
                 }
             }
             }
+
+            // Trigger a full screen redraw by sending a resize event
+            let (width, height) = miniquad::window::screen_size();
+            self.event_pub.notify_resize(Dimension::from([width, height]));
         }
         }
 
 
+        let span = span!(Level::TRACE, "process");
+        let _enter = span.enter();
+
         // Process as many methods as we can
         // Process as many methods as we can
+        let methods = std::mem::take(&mut *self.method_queue.lock());
         for (epoch, method) in methods {
         for (epoch, method) in methods {
             if DEBUG_TRAX {
             if DEBUG_TRAX {
                 self.trax_method(epoch, &method);
                 self.trax_method(epoch, &method);

+ 1 - 1
bin/app/src/ui/layer.rs

@@ -116,7 +116,7 @@ impl Layer {
             for child in self.get_children() {
             for child in self.get_children() {
                 let obj = get_ui_object3(&child);
                 let obj = get_ui_object3(&child);
                 let Some(mut draw_update) = obj.draw(rect, atom).await else {
                 let Some(mut draw_update) = obj.draw(rect, atom).await else {
-                    t!("{child:?} draw returned none");
+                    //t!("{child:?} draw returned none");
                     continue
                     continue
                 };
                 };
 
 

+ 1 - 1
bin/app/src/ui/vector_art/mod.rs

@@ -99,7 +99,7 @@ impl VectorArt {
 
 
     fn get_draw_instrs(&self) -> Vec<DrawInstruction> {
     fn get_draw_instrs(&self) -> Vec<DrawInstruction> {
         if !self.is_visible.get() {
         if !self.is_visible.get() {
-            t!("Skipping draw for invisible node");
+            //t!("Skipping draw for invisible node");
             return vec![]
             return vec![]
         }
         }
 
 

+ 1 - 1
bin/app/src/ui/win.rs

@@ -453,7 +453,7 @@ impl Window {
         for child in self.get_children() {
         for child in self.get_children() {
             let obj = get_ui_object3(&child);
             let obj = get_ui_object3(&child);
             let Some(mut draw_update) = obj.draw(rect, atom).await else {
             let Some(mut draw_update) = obj.draw(rect, atom).await else {
-                t!("{child:?} draw returned none");
+                //t!("{child:?} draw returned none");
                 continue
                 continue
             };
             };