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

app/gfx: replace screen_was_off bool with an actual ScreenState state machine enum that keeps track of the screen state lifecycle as we switch it off and on transitioning between states. We need this to ensure proper ordering of queueing buffered operations, restarting processes and other such operations.

darkfi 8 месяцев назад
Родитель
Сommit
cb0193dd56
1 измененных файлов с 207 добавлено и 105 удалено
  1. 207 105
      bin/app/src/gfx/mod.rs

+ 207 - 105
bin/app/src/gfx/mod.rs

@@ -925,7 +925,7 @@ struct Stage {
     event_pub: GraphicsEventPublisherPtr,
 
     pruner: PruneMethodHeap,
-    screen_was_off: bool,
+    screen_state: ScreenState,
     #[cfg(target_os = "android")]
     ex: ExecutorPtr,
     #[cfg(target_os = "android")]
@@ -1030,15 +1030,19 @@ impl Stage {
             event_pub,
 
             pruner: PruneMethodHeap::new(epoch),
-            screen_was_off: false,
+            screen_state: ScreenState::On,
             #[cfg(target_os = "android")]
-            ex,
+            ex: ex.clone(),
             #[cfg(target_os = "android")]
             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_.start_refresh_task();
+
         self_
     }
 
@@ -1318,6 +1322,107 @@ impl Stage {
         #[cfg(not(target_os = "android"))]
         false
     }
+
+    fn process_methods(&mut self) {
+        let span = span!(Level::TRACE, "process");
+        let _enter = span.enter();
+
+        // Process as many methods as we can
+        let methods = std::mem::take(&mut *self.method_queue.lock());
+        for (epoch, method) in methods {
+            if DEBUG_TRAX {
+                self.trax_method(epoch, &method);
+            }
+            if epoch < self.epoch {
+                if DEBUG_TRAX {
+                    let mut trax = get_trax().lock();
+                    trax.put_stat(1);
+                    trax.flush();
+                }
+                // Discard old rubbish
+                trace!(target: "gfx", "Discard method with old epoch: {epoch} curr: {} [method={method:?}]", self.epoch);
+                continue
+            }
+            assert_eq!(epoch, self.epoch);
+            self.process_method(method);
+            if DEBUG_TRAX {
+                get_trax().lock().flush();
+            }
+        }
+    }
+
+    fn prime_screen(&mut self) {
+        let span = span!(Level::TRACE, "pruner");
+        let _enter = span.enter();
+
+        let methods = self.pruner.recv_all();
+        assert!(self.pending_batches.is_empty());
+        // Process all cached methods by the pruner from while the screen was off.
+        for method in methods {
+            // Stale methods will be dropped by pruner, so they will not be caught by trax
+            // while the screen is off.
+            if DEBUG_TRAX {
+                self.trax_method(self.epoch, &method);
+            }
+            // We discard batches here but process_method uses them so implement this
+            // workaround.
+            match method {
+                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!")
+                }
+
+                GraphicsMethod::Noop => panic!("noop"),
+            }
+            if DEBUG_TRAX {
+                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]));
+    }
+
+    fn close_pending_batches(&mut self) {
+        // Immediately apply any pending batches when the screen is switched off
+        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 {
+            self.process_method(GraphicsMethod::EndBatch { batch_id, timest: unixtime() });
+            if !self.dropped_batches.insert(batch_id) {
+                panic!("dropped batch {batch_id} already exits!");
+            }
+        }
+        assert!(self.pending_batches.is_empty());
+    }
+
+    fn start_refresh_task(&mut self) {
+        #[cfg(target_os = "android")]
+        {
+            assert!(self.refresh_task.is_none());
+            // For animations do periodic refresh every 40 ms
+            self.refresh_task = Some(self.ex.spawn(async move {
+                loop {
+                    darkfi::system::msleep(40).await;
+                    miniquad::window::schedule_update();
+                }
+            }));
+        }
+    }
 }
 
 /// This is used to process the method queue while the screen is off to avoid the queue
@@ -1354,12 +1459,16 @@ impl PruneMethodHeap {
         }
     }
 
+    #[instrument(skip_all, target = "gfx::pruner")]
     fn drain(&mut self, methods: Vec<(EpochIndex, GraphicsMethod)>) {
         // Process as many methods as we can
         for (epoch, method) in methods {
             if epoch < self.epoch {
                 // Discard old rubbish
-                trace!(target: "gfx::pruner", "Discard method with old epoch: {epoch} curr: {} [method={method:?}]", self.epoch);
+                t!(
+                    "Discard method with old epoch: {epoch} curr: {} [method={method:?}]",
+                    self.epoch
+                );
                 continue
             }
             assert_eq!(epoch, self.epoch);
@@ -1472,7 +1581,7 @@ impl PruneMethodHeap {
                     if old_val.1 < timest {
                         *old_val = (batch_id, timest, val);
                     } else {
-                        trace!(target: "gfx::pruner", "Rejected stale draw_call {key}: {val:?}");
+                        t!("Rejected stale draw_call {key}: {val:?}");
                     }
                 }
                 None => {
@@ -1504,124 +1613,117 @@ impl PruneMethodHeap {
     }
 }
 
-impl EventHandler for Stage {
-    fn update(&mut self) {
-        // todo: trax is all messed up in this func
+#[derive(Copy, Clone, Debug, PartialEq)]
+enum ScreenState {
+    // Screen is on as normal
+    On,
+    // First update since screen has gone off
+    SwitchOff,
+    // Subsequent updates after SwitchOff state
+    Off,
+    // First update when screen is on but before draw has been called
+    ReadyOn,
+    // First update and draw has been called
+    PrimedOn,
+    // Second update ready to process pruned buffered allocs
+    SwitchOn,
+    // Invalid state
+    Noop,
+}
 
-        if self.egl_ctx_is_disabled() {
-            #[cfg(target_os = "android")]
-            {
-                self.refresh_task = None;
+impl ScreenState {
+    fn update(&mut self, egl_is_disabled: bool) {
+        use ScreenState::*;
+        *self = if egl_is_disabled {
+            match self {
+                On | ReadyOn | PrimedOn | SwitchOn => SwitchOff,
+                SwitchOff => Off,
+                Off => Off,
+                Noop => panic!("noop"),
             }
-            if !self.screen_was_off {
-                d!("Screen is switched off");
+        } else {
+            match self {
+                SwitchOff | Off => ReadyOn,
+                ReadyOn => PrimedOn,
+                PrimedOn => SwitchOn,
+                SwitchOn => On,
+                On => On,
+                Noop => panic!("noop"),
             }
+        };
+    }
+}
 
-            // Immediately apply any pending batches when the screen is switched off
-            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 {
-                self.process_method(GraphicsMethod::EndBatch { batch_id, timest: unixtime() });
-                if !self.dropped_batches.insert(batch_id) {
-                    panic!("dropped batch {batch_id} already exits!");
-                }
-            }
-            assert!(self.pending_batches.is_empty());
+impl EventHandler for Stage {
+    fn update(&mut self) {
+        // todo: trax is all messed up in this func
 
-            // 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.screen_was_off = true;
-            return
+        let old_screen_state = self.screen_state;
+        self.screen_state.update(self.egl_ctx_is_disabled());
+        if self.screen_state != old_screen_state {
+            d!("Switching screen state {old_screen_state:?} => {:?}", self.screen_state);
         }
 
-        #[cfg(target_os = "android")]
-        if self.refresh_task.is_none() {
-            // For animations do periodic refresh every 40 ms
-            self.refresh_task = Some(self.ex.spawn(async move {
-                loop {
-                    darkfi::system::msleep(40).await;
-                    miniquad::window::schedule_update();
+        match self.screen_state {
+            ScreenState::SwitchOff => {
+                #[cfg(target_os = "android")]
+                {
+                    self.refresh_task = None;
                 }
-            }));
-        }
 
-        // We actually want to skip draining the prune queue the first time 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 {
-            self.screen_was_off = false;
-            d!("Screen is switched on");
-            return
-        } else {
-            let span = span!(Level::TRACE, "pruner");
-            let _enter = span.enter();
-
-            let methods = self.pruner.recv_all();
-            assert!(methods.is_empty() || self.pending_batches.is_empty());
-            // Process all cached methods by the pruner from while the screen was off.
-            for method in methods {
-                // Stale methods will be dropped by pruner, so they will not be caught by trax
-                // while the screen is off.
-                if DEBUG_TRAX {
-                    self.trax_method(self.epoch, &method);
-                }
-                // We discard batches here but process_method uses them so implement this
-                // workaround.
-                match method {
-                    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.close_pending_batches();
 
-                    GraphicsMethod::Noop => panic!("noop"),
-                }
-                if DEBUG_TRAX {
-                    get_trax().lock().flush();
-                }
+                // Screen is off so collect all methods into the pruner
+                let methods = std::mem::take(&mut *self.method_queue.lock());
+                self.pruner.drain(methods);
             }
+            ScreenState::Off => {
+                #[cfg(target_os = "android")]
+                {
+                    assert!(self.refresh_task.is_none());
+                }
 
-            // 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]));
-        }
+                assert!(self.pending_batches.is_empty());
 
-        let span = span!(Level::TRACE, "process");
-        let _enter = span.enter();
+                // Screen is off so collect all methods into the pruner
+                let methods = std::mem::take(&mut *self.method_queue.lock());
+                self.pruner.drain(methods);
+            }
+            ScreenState::ReadyOn => {
+                self.start_refresh_task();
+                // We actually want to skip draining the prune queue the first time 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.
+            }
+            ScreenState::PrimedOn => {
+                #[cfg(target_os = "android")]
+                {
+                    assert!(self.refresh_task.is_some());
+                }
 
-        // Process as many methods as we can
-        let methods = std::mem::take(&mut *self.method_queue.lock());
-        for (epoch, method) in methods {
-            if DEBUG_TRAX {
-                self.trax_method(epoch, &method);
+                self.prime_screen();
             }
-            if epoch < self.epoch {
-                if DEBUG_TRAX {
-                    let mut trax = get_trax().lock();
-                    trax.put_stat(1);
-                    trax.flush();
+            ScreenState::SwitchOn => {
+                #[cfg(target_os = "android")]
+                {
+                    assert!(self.refresh_task.is_some());
                 }
-                // Discard old rubbish
-                trace!(target: "gfx", "Discard method with old epoch: {epoch} curr: {} [method={method:?}]", self.epoch);
-                continue
+
+                // This should have been cleared in previous PrimedOn state
+                let methods = self.pruner.recv_all();
+                assert!(methods.is_empty());
+
+                self.process_methods();
             }
-            assert_eq!(epoch, self.epoch);
-            self.process_method(method);
-            if DEBUG_TRAX {
-                get_trax().lock().flush();
+            ScreenState::On => {
+                #[cfg(target_os = "android")]
+                {
+                    assert!(self.refresh_task.is_some());
+                }
+
+                self.process_methods();
             }
+            ScreenState::Noop => panic!("noop screen state"),
         }
     }