ソースを参照

app/gfx: introduce RenderApiSync to use in handle_touch_sync() for zero-latency gfx API calls

jkds 6 ヶ月 前
コミット
d1747c78f7

+ 52 - 2
bin/app/src/gfx/api.rs

@@ -22,8 +22,8 @@ use std::sync::{
 };
 
 use super::{
-    anim::Frame as AnimFrame, AnimId, BufferId, DebugTag, DrawCall, TextureFormat, TextureId,
-    Vertex, NEXT_ANIM_ID, NEXT_BUFFER_ID, NEXT_TEXTURE_ID,
+    anim::Frame as AnimFrame, AnimId, BufferId, DebugTag, DrawCall, Stage, TextureFormat,
+    TextureId, Vertex,
 };
 use crate::{
     prop::{BatchGuardId, PropertyAtomicGuard},
@@ -33,6 +33,10 @@ use crate::{
 pub type EpochIndex = u32;
 type DcId = u64;
 
+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>;
 pub type ManagedBufferPtr = Arc<ManagedBuffer>;
 pub type ManagedSeqAnimPtr = Arc<ManagedSeqAnim>;
@@ -323,3 +327,49 @@ impl Default for GraphicsMethod {
         GraphicsMethod::Noop
     }
 }
+
+pub struct RenderApiSync<'a> {
+    stage: &'a mut Stage,
+}
+
+impl<'a> RenderApiSync<'a> {
+    pub fn new(stage: &'a mut Stage) -> Self {
+        Self { stage }
+    }
+
+    // Texture methods
+    pub fn new_texture(
+        &mut self,
+        width: u16,
+        height: u16,
+        data: Vec<u8>,
+        fmt: TextureFormat,
+        tag: DebugTag,
+    ) -> ManagedTexturePtr {
+        let render_api = self.stage.render_api.clone();
+        let id = NEXT_TEXTURE_ID.fetch_add(1, Ordering::Relaxed);
+        self.stage.method_new_texture(width, height, &data, fmt, id);
+        Arc::new(ManagedTexture { id, epoch: 0, render_api, tag })
+    }
+
+    // Buffer methods
+    pub fn new_vertex_buffer(&mut self, verts: Vec<Vertex>, tag: DebugTag) -> ManagedBufferPtr {
+        let render_api = self.stage.render_api.clone();
+        let id = NEXT_BUFFER_ID.fetch_add(1, Ordering::Relaxed);
+        self.stage.method_new_vertex_buffer(&verts, id);
+        Arc::new(ManagedBuffer { id, epoch: 0, render_api, tag, buftype: 0 })
+    }
+
+    pub fn new_index_buffer(&mut self, indices: Vec<u16>, tag: DebugTag) -> ManagedBufferPtr {
+        let render_api = self.stage.render_api.clone();
+        let id = NEXT_BUFFER_ID.fetch_add(1, Ordering::Relaxed);
+        self.stage.method_new_index_buffer(&indices, id);
+        Arc::new(ManagedBuffer { id, epoch: 0, render_api, tag, buftype: 1 })
+    }
+
+    // Draw calls (no batching)
+    pub fn replace_draw_calls(&mut self, dcs: Vec<(DcId, DrawCall)>) {
+        let timest = unixtime();
+        self.stage.method_replace_draw_calls(timest, dcs);
+    }
+}

+ 31 - 17
bin/app/src/gfx/mod.rs

@@ -43,7 +43,7 @@ use anim::{Frame as AnimFrame, GfxSeqAnim};
 mod api;
 pub use api::{
     EpochIndex, GraphicsMethod, ManagedBuffer, ManagedBufferPtr, ManagedSeqAnim, ManagedSeqAnimPtr,
-    ManagedTexture, ManagedTexturePtr, RenderApi,
+    ManagedTexture, ManagedTexturePtr, RenderApi, RenderApiSync,
 };
 mod ev;
 pub use ev::{
@@ -116,10 +116,6 @@ 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);
-
 #[derive(Clone, Debug)]
 pub struct DrawMesh {
     pub vertex_buffer: ManagedBufferPtr,
@@ -626,6 +622,7 @@ struct Stage {
     epoch: EpochIndex,
     method_recv: async_channel::Receiver<(EpochIndex, GraphicsMethod)>,
     event_pub: GraphicsEventPublisherPtr,
+    render_api: RenderApi,
 
     pruner: PruneMethodHeap,
     screen_state: ScreenState,
@@ -643,7 +640,8 @@ impl Stage {
 
         let god = GOD.get().unwrap();
         // Start a new epoch. This is a brand new UI run.
-        let epoch = god.render_api.next_epoch();
+        let render_api = god.render_api.clone();
+        let epoch = render_api.next_epoch();
         // This will start the app to start. Needed since we cannot get window size for init
         // until window is created.
         god.start_app(epoch);
@@ -680,6 +678,7 @@ impl Stage {
             epoch,
             method_recv,
             event_pub,
+            render_api,
 
             pruner: PruneMethodHeap::new(epoch),
             screen_state: ScreenState::On,
@@ -778,7 +777,7 @@ impl Stage {
         }
     }
 
-    fn method_new_texture(
+    pub(self) fn method_new_texture(
         &mut self,
         width: u16,
         height: u16,
@@ -826,7 +825,7 @@ impl Stage {
             get_trax().lock().put_stat(0);
         }
     }
-    fn method_delete_texture(&mut self, gfx_texture_id: TextureId) {
+    pub(self) fn method_delete_texture(&mut self, gfx_texture_id: TextureId) {
         let Some(texture) = self.textures.remove(&gfx_texture_id) else {
             if DEBUG_TRAX {
                 get_trax().lock().put_stat(2);
@@ -841,7 +840,7 @@ impl Stage {
             get_trax().lock().put_stat(0);
         }
     }
-    fn method_new_vertex_buffer(&mut self, verts: &[Vertex], gfx_buffer_id: BufferId) {
+    pub(self) fn method_new_vertex_buffer(&mut self, verts: &[Vertex], gfx_buffer_id: BufferId) {
         let buffer = self.ctx.new_buffer(
             BufferType::VertexBuffer,
             BufferUsage::Immutable,
@@ -862,7 +861,7 @@ impl Stage {
             get_trax().lock().put_stat(0);
         }
     }
-    fn method_new_index_buffer(&mut self, indices: &[u16], gfx_buffer_id: BufferId) {
+    pub(self) fn method_new_index_buffer(&mut self, indices: &[u16], gfx_buffer_id: BufferId) {
         let buffer = self.ctx.new_buffer(
             BufferType::IndexBuffer,
             BufferUsage::Immutable,
@@ -883,7 +882,7 @@ impl Stage {
             get_trax().lock().put_stat(0);
         }
     }
-    fn method_delete_buffer(&mut self, gfx_buffer_id: BufferId) {
+    pub(self) fn method_delete_buffer(&mut self, gfx_buffer_id: BufferId) {
         let Some(buffer) = self.buffers.remove(&gfx_buffer_id) else {
             if DEBUG_TRAX {
                 get_trax().lock().put_stat(2);
@@ -898,7 +897,7 @@ impl Stage {
             get_trax().lock().put_stat(0);
         }
     }
-    fn method_new_anim(&mut self, gfx_anim_id: AnimId, frames_len: usize, oneshot: bool) {
+    pub(self) fn method_new_anim(&mut self, gfx_anim_id: AnimId, frames_len: usize, oneshot: bool) {
         if DEBUG_GFXAPI {
             d!("Invoked method: new_anim({gfx_anim_id}, {frames_len}, {oneshot})");
         }
@@ -906,7 +905,12 @@ impl Stage {
             panic!("Duplicate anim ID={gfx_anim_id} detected!");
         }
     }
-    fn method_update_anim(&mut self, gfx_anim_id: AnimId, frame_idx: usize, frame: AnimFrame) {
+    pub(self) 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 {
             panic!("couldn't find anim {gfx_anim_id}");
         };
@@ -915,7 +919,7 @@ impl Stage {
         }
         anim.set(frame_idx, frame, &self.textures, &self.buffers);
     }
-    fn method_delete_anim(&mut self, gfx_anim_id: AnimId) {
+    pub(self) fn method_delete_anim(&mut self, gfx_anim_id: AnimId) {
         let Some(anim) = self.anims.remove(&gfx_anim_id) else {
             panic!("couldn't find anim {gfx_anim_id}");
         };
@@ -923,7 +927,11 @@ impl Stage {
             d!("Invoked method: delete_anim({} => {:?})", gfx_anim_id, anim);
         }
     }
-    fn method_replace_draw_calls(&mut self, batch_timest: Timestamp, dcs: Vec<(DcId, DrawCall)>) {
+    pub(self) fn method_replace_draw_calls(
+        &mut self,
+        batch_timest: Timestamp,
+        dcs: Vec<(DcId, DrawCall)>,
+    ) {
         if DEBUG_GFXAPI {
             d!("Invoked method: replace_draw_calls({:?})", dcs);
         }
@@ -1271,11 +1279,17 @@ impl EventHandler for Stage {
             self.window_node = god.app.sg_root.lookup_node("/window");
         }
 
+        // Clone window_node to avoid borrow conflict with RenderApiSync
+        let window_node = self.window_node.clone();
+
+        // Create RenderApiSync for direct graphics operations
+        let mut render_api_sync = RenderApiSync::new(self);
+
         // Direct call to Window's handle_touch_event_sync
-        if let Some(window_node) = &self.window_node {
+        if let Some(window_node) = &window_node {
             match window_node.pimpl() {
                 Pimpl::Window(win) => {
-                    if win.handle_touch_sync(phase, id, pos) {
+                    if win.handle_touch_sync(&mut render_api_sync, phase, id, pos) {
                         return
                     }
                 }

+ 11 - 2
bin/app/src/ui/edit/mod.rs

@@ -37,7 +37,10 @@ use tracing::instrument;
 #[cfg(target_os = "android")]
 use crate::android::textinput::AndroidTextInputState;
 use crate::{
-    gfx::{gfxtag, DrawCall, DrawInstruction, DrawMesh, Point, Rectangle, RenderApi, Vertex},
+    gfx::{
+        gfxtag, DrawCall, DrawInstruction, DrawMesh, Point, Rectangle, RenderApi, RenderApiSync,
+        Vertex,
+    },
     mesh::MeshBuilder,
     prop::{
         BatchGuardId, BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyColor,
@@ -1823,7 +1826,13 @@ impl UIObject for BaseEdit {
         true
     }
 
-    fn handle_touch_sync(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
+    fn handle_touch_sync(
+        &self,
+        _render_api: &mut RenderApiSync,
+        phase: TouchPhase,
+        id: u64,
+        touch_pos: Point,
+    ) -> bool {
         if !self.is_active.get() {
             return false
         }

+ 9 - 3
bin/app/src/ui/layer.rs

@@ -24,7 +24,7 @@ use std::sync::Arc;
 use tracing::instrument;
 
 use crate::{
-    gfx::{DrawCall, DrawInstruction, Point, Rectangle, RenderApi},
+    gfx::{DrawCall, DrawInstruction, Point, Rectangle, RenderApi, RenderApiSync},
     prop::{BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyRect, PropertyUint32, Role},
     scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
     util::i18n::I18nBabelFish,
@@ -307,14 +307,20 @@ impl UIObject for Layer {
         false
     }
 
-    fn handle_touch_sync(&self, phase: TouchPhase, id: u64, mut touch_pos: Point) -> bool {
+    fn handle_touch_sync(
+        &self,
+        render_api: &mut RenderApiSync,
+        phase: TouchPhase,
+        id: u64,
+        mut touch_pos: Point,
+    ) -> bool {
         if !self.is_visible.get() {
             return false
         }
         touch_pos -= self.rect.get().pos();
         for child in self.get_children() {
             let obj = get_ui_object3(&child);
-            if obj.handle_touch_sync(phase, id, touch_pos) {
+            if obj.handle_touch_sync(render_api, phase, id, touch_pos) {
                 return true
             }
         }

+ 29 - 5
bin/app/src/ui/menu.rs

@@ -32,7 +32,7 @@ use std::{
 };
 
 use crate::{
-    gfx::{gfxtag, DrawCall, DrawInstruction, Point, Rectangle, RenderApi},
+    gfx::{gfxtag, DrawCall, DrawInstruction, Point, Rectangle, RenderApi, RenderApiSync},
     mesh::MeshBuilder,
     prop::{
         BatchGuardId, BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyColor,
@@ -330,6 +330,25 @@ impl Menu {
         self.render_api.replace_draw_calls(None, draw_calls);
     }
 
+    fn redraw_scroll_sync(&self, render_api: &mut RenderApiSync) {
+        let rect = self.rect.get();
+        let scroll = self.scroll.load(Ordering::Relaxed);
+
+        // Only recreate root with updated scroll position
+        let root_instrs =
+            vec![DrawInstruction::ApplyView(rect), DrawInstruction::Move(Point::new(0., -scroll))];
+
+        let root_dc = DrawCall {
+            instrs: root_instrs,
+            dcs: vec![self.content_dc_key],
+            z_index: self.z_index.get(),
+            debug_str: "menu_root",
+        };
+
+        let draw_calls = vec![(self.root_dc_key, root_dc)];
+        render_api.replace_draw_calls(draw_calls);
+    }
+
     fn scrollview(&self, scroll: f32) {
         let item_height = self.get_item_height();
         let num_items = self.items.get_len() as f32;
@@ -342,9 +361,6 @@ impl Menu {
         let overscroll = rect.h * 0.5;
         let scroll = scroll.clamp(0., max_scroll + overscroll);
         self.scroll.store(scroll, Ordering::Relaxed);
-
-        // Only update root draw call with new scroll position
-        self.redraw_scroll();
     }
 
     fn start_scroll(&self, delta: f32) {
@@ -367,6 +383,7 @@ impl Menu {
             while speed.abs() >= EPSILON {
                 let scroll = self.scroll.load(Ordering::Relaxed);
                 self.scrollview(scroll + speed);
+                self.redraw_scroll();
                 speed *= resist;
                 self.speed.store(speed, Ordering::Relaxed);
                 darkfi::system::msleep(16).await;
@@ -475,7 +492,13 @@ impl UIObject for Menu {
         false
     }
 
-    fn handle_touch_sync(&self, phase: TouchPhase, id: u64, touch_pos: Point) -> bool {
+    fn handle_touch_sync(
+        &self,
+        render_api: &mut RenderApiSync,
+        phase: TouchPhase,
+        id: u64,
+        touch_pos: Point,
+    ) -> bool {
         if id != 0 {
             return false
         }
@@ -516,6 +539,7 @@ impl UIObject for Menu {
                 };
 
                 self.scrollview(scroll);
+                self.redraw_scroll_sync(render_api);
                 true
             }
 

+ 8 - 2
bin/app/src/ui/mod.rs

@@ -22,7 +22,7 @@ use miniquad::{KeyCode, KeyMods, MouseButton, TouchPhase};
 use std::sync::{Arc, Weak};
 
 use crate::{
-    gfx::{DrawCall, Point, Rectangle},
+    gfx::{DrawCall, Point, Rectangle, RenderApiSync},
     prop::{BatchGuardPtr, ModifyAction, PropertyAtomicGuard, PropertyPtr, Role},
     scene::{Pimpl, SceneNode as SceneNode3, SceneNodePtr, SceneNodeWeak},
     util::i18n::I18nBabelFish,
@@ -110,7 +110,13 @@ pub trait UIObject: Sync {
         false
     }
 
-    fn handle_touch_sync(&self, _phase: TouchPhase, _id: u64, _touch_pos: Point) -> bool {
+    fn handle_touch_sync(
+        &self,
+        _render_api: &mut RenderApiSync,
+        _phase: TouchPhase,
+        _id: u64,
+        _touch_pos: Point,
+    ) -> bool {
         false
     }
 

+ 9 - 3
bin/app/src/ui/win.rs

@@ -27,7 +27,7 @@ use crate::{
         gfxtag, DrawCall, DrawInstruction, GraphicsEventCharSub, GraphicsEventKeyDownSub,
         GraphicsEventKeyUpSub, GraphicsEventMouseButtonDownSub, GraphicsEventMouseButtonUpSub,
         GraphicsEventMouseMoveSub, GraphicsEventMouseWheelSub, GraphicsEventPublisherPtr,
-        GraphicsEventTouchSub, Point, Rectangle, RenderApi,
+        GraphicsEventTouchSub, Point, Rectangle, RenderApi, RenderApiSync,
     },
     prop::{
         BatchGuardPtr, PropertyAtomicGuard, PropertyDimension, PropertyFloat32, PropertyStr, Role,
@@ -467,11 +467,17 @@ impl Window {
         }
     }
 
-    pub fn handle_touch_sync(&self, phase: TouchPhase, id: u64, mut touch_pos: Point) -> bool {
+    pub fn handle_touch_sync(
+        &self,
+        render_api: &mut RenderApiSync,
+        phase: TouchPhase,
+        id: u64,
+        mut touch_pos: Point,
+    ) -> bool {
         self.local_scale(&mut touch_pos);
         for child in self.get_children() {
             let obj = get_ui_object3(&child);
-            if obj.handle_touch_sync(phase, id, touch_pos) {
+            if obj.handle_touch_sync(render_api, phase, id, touch_pos) {
                 return true
             }
         }