Răsfoiți Sursa

app: add trax hooks for generating data dumps of gfx subsystem and pytools to analyze them

darkfi 1 an în urmă
părinte
comite
54fbb1c016

+ 10 - 0
bin/app/pydrk/serial.py

@@ -61,6 +61,12 @@ class Cursor:
             raise Exception("invalid read")
             raise Exception("invalid read")
         return slice
         return slice
 
 
+    def remain_data(self):
+        return self.by[self.i:]
+
+    def is_end(self):
+        return not bool(self.remain_data())
+
 def read_u8(cur):
 def read_u8(cur):
     b = cur.read(1)
     b = cur.read(1)
     return int.from_bytes(b, "little")
     return int.from_bytes(b, "little")
@@ -81,6 +87,10 @@ def read_f32(cur):
     by = cur.read(4)
     by = cur.read(4)
     return struct.unpack("<f", by)[0]
     return struct.unpack("<f", by)[0]
 
 
+def read_i32(cur):
+    by = cur.read(4)
+    return struct.unpack("<i", by)[0]
+
 def decode_varint(cur):
 def decode_varint(cur):
     n = read_u8(cur)
     n = read_u8(cur)
     match n:
     match n:

+ 296 - 0
bin/app/script/traxator.py

@@ -0,0 +1,296 @@
+#!/usr/bin/python
+from pydrk import serial
+from collections import namedtuple
+from dataclasses import dataclass
+from typing import Union
+
+@dataclass
+class SetScale:
+    scale: float
+
+@dataclass
+class Move:
+    x: float
+    y: float
+
+@dataclass
+class SetPos:
+    x: float
+    y: float
+
+@dataclass
+class ApplyView:
+    x: float
+    y: float
+    w: float
+    h: float
+
+@dataclass
+class Draw:
+    vert_id: int
+    vert_epoch: int
+    vert_tag: str
+    vert_buftype: int
+    index_id: int
+    index_epoch: int
+    index_tag: str
+    index_buftype: int
+    tex: (int, int, str)
+    num_elements: int
+
+Instr = Union[SetScale, Move, SetPos, ApplyView, Draw]
+
+def hex(dat):
+    return " ".join(f"{b:02x}" for b in dat)
+
+def read_tag(cur):
+    return serial.decode_str(cur)
+
+DrawCall = namedtuple("DrawCall", [
+    "dc_id",
+    "instrs",
+    "dcs",
+    "z_index",
+    "debug_str"
+])
+
+def read_dc(cur):
+    dc_id = serial.read_u64(cur)
+    instrs = serial.decode_arr(cur, read_instr)
+    dcs = serial.decode_arr(cur, serial.read_u64)
+    z_index = serial.read_u32(cur)
+    debug_str = serial.decode_str(cur)
+    return DrawCall(
+        dc_id,
+        instrs,
+        dcs,
+        z_index,
+        debug_str
+    )
+
+def read_instr(cur):
+    enum = serial.read_u8(cur)
+    match enum:
+        case 0:
+            scale = serial.read_f32(cur)
+            return SetScale(scale)
+        case 1:
+            x = serial.read_f32(cur)
+            y = serial.read_f32(cur)
+            return Move(x, y)
+        case 2:
+            x = serial.read_f32(cur)
+            y = serial.read_f32(cur)
+            #print(f"  set_pos x={x}, y={y}")
+            return SetPos(x, y)
+        case 3:
+            x = serial.read_f32(cur)
+            y = serial.read_f32(cur)
+            w = serial.read_f32(cur)
+            h = serial.read_f32(cur)
+            #print(f"  apply_view x={x}, y={y}, w={w}, h={h}")
+            return ApplyView(x, y, w, h)
+        case 4:
+            vert_id = serial.read_u32(cur)
+            vert_epoch = serial.read_u32(cur)
+            vert_tag = serial.decode_opt(cur, read_tag)
+            vert_buftype = serial.read_u8(cur)
+            index_id = serial.read_u32(cur)
+            index_epoch = serial.read_u32(cur)
+            index_tag = serial.decode_opt(cur, read_tag)
+            index_buftype = serial.read_u8(cur)
+            def read_tex(cur):
+                id = serial.read_u32(cur)
+                epoch = serial.read_u32(cur)
+                tag = serial.decode_opt(cur, read_tag)
+                return (id, epoch, tag)
+            tex = serial.decode_opt(cur, read_tex)
+            num_elements = serial.read_i32(cur)
+            return Draw(
+                vert_id,
+                vert_epoch,
+                vert_tag,
+                vert_buftype,
+                index_id,
+                index_epoch,
+                index_tag,
+                index_buftype,
+                tex,
+                num_elements
+            )
+        case _:
+            raise NotImplementedError
+
+@dataclass
+class Vertex:
+    x: float
+    y: float
+    r: float
+    g: float
+    b: float
+    a: float
+    u: float
+    v: float
+
+@dataclass
+class PutDrawCall:
+    epoch: int
+    timest: int
+    dcs: [DrawCall]
+    stats: [int]
+
+@dataclass
+class PutTex:
+    epoch: int
+    tex: int
+    tag: str
+    stat: int
+
+@dataclass
+class PutVerts:
+    epoch: int
+    verts: [Vertex]
+    buf: int
+    tag: str
+    buftype: int
+    stat: int
+
+@dataclass
+class PutIdxs:
+    epoch: int
+    idxs: [int]
+    buf: int
+    tag: str
+    buftype: int
+    stat: int
+
+@dataclass
+class DelTex:
+    epoch: int
+    buf: int
+    tag: str
+    stat: int
+
+@dataclass
+class DelBuf:
+    epoch: int
+    buf: int
+    tag: str
+    buftype: int
+    stat: int
+
+@dataclass
+class SetCurr:
+    dc: int
+
+@dataclass
+class SetInstr:
+    idx: int
+
+Section = Union[PutDrawCall, PutTex, PutVerts, PutIdxs, DelTex, DelBuf, SetCurr, SetInstr]
+
+def read_vert(cur):
+    return Vertex(
+        serial.read_f32(cur),
+        serial.read_f32(cur),
+        serial.read_f32(cur),
+        serial.read_f32(cur),
+        serial.read_f32(cur),
+        serial.read_f32(cur),
+        serial.read_f32(cur),
+        serial.read_f32(cur),
+    )
+
+def read_section(f):
+    fpos = f.tell()
+    buf = serial.decode_buf(f)
+    if not buf:
+        return None
+    cur = serial.Cursor(buf)
+    c = serial.read_u8(cur)
+    #print(f"SECTION: {c} {len(buf)}B [{fpos}]")
+    #print(hex(cur.by))
+    match c:
+        case 0:
+            epoch = serial.read_u32(cur)
+            timest = serial.read_u64(cur)
+            dcs = serial.decode_arr(cur, read_dc)
+            stats = []
+            for _ in dcs:
+                stat = serial.read_u8(cur)
+                stats.append(stat)
+                #print(f"  stat={stat}")
+            #print(f"put_dcs epoch={epoch}, timest={timest}, dcs={dcs}, stats={stats}")
+            sect = PutDrawCall(epoch, timest, dcs, stats)
+        case 1:
+            epoch = serial.read_u32(cur)
+            tex = serial.read_u32(cur)
+            tag = serial.decode_opt(cur, read_tag)
+            stat = serial.read_u8(cur)
+            #print(f"put_tex epoch={epoch}, tex={tex}, tag='{tag}', stat={stat}")
+            sect = PutTex(epoch, tex, tag, stat)
+        case 2:
+            epoch = serial.read_u32(cur)
+            verts = serial.decode_arr(cur, read_vert)
+            buf = serial.read_u32(cur)
+            tag = serial.decode_opt(cur, read_tag)
+            buftype = serial.read_u8(cur)
+            stat = serial.read_u8(cur)
+            #print(f"put_verts epoch={epoch}, buf={buf}, tag='{tag}', buftype={buftype}, stat={stat}")
+            sect = PutVerts(epoch, verts, buf, tag, buftype, stat)
+        case 3:
+            epoch = serial.read_u32(cur)
+            idxs = serial.decode_arr(cur, serial.read_u16)
+            buf = serial.read_u32(cur)
+            tag = serial.decode_opt(cur, read_tag)
+            buftype = serial.read_u8(cur)
+            stat = serial.read_u8(cur)
+            #print(f"put_idxs epoch={epoch}, buf={buf}, tag='{tag}', buftype={buftype}, stat={stat}")
+            sect = PutIdxs(epoch, idxs, buf, tag, buftype, stat)
+        case 4:
+            epoch = serial.read_u32(cur)
+            buf = serial.read_u32(cur)
+            tag = serial.decode_opt(cur, read_tag)
+            stat = serial.read_u8(cur)
+            #print(f"del_tex epoch={epoch}, buf={buf}, tag='{tag}', stat={stat}")
+            sect = DelTex(epoch, buf, tag, stat)
+        case 5:
+            epoch = serial.read_u32(cur)
+            buf = serial.read_u32(cur)
+            tag = serial.decode_opt(cur, read_tag)
+            buftype = serial.read_u8(cur)
+            stat = serial.read_u8(cur)
+            #print(f"del_buf epoch={epoch}, buf={buf}, tag='{tag}', buftype={buftype}, stat={stat}")
+            sect = DelBuf(epoch, buf, tag, buftype, stat)
+        case 6:
+            dc = serial.read_u64(cur)
+            #print(f"set_curr dc={dc}")
+            sect = SetCurr(dc)
+        case 7:
+            idx = serial.read_u64(cur)
+            #print(f"set_instr idx={idx}")
+            sect = SetInstr(idx)
+        case _:
+            raise NotImplementedError
+
+    # Crash out if we didn't fully consume the buffer
+    if not cur.is_end():
+        print(hex(cur.remain_data()))
+    assert cur.is_end()
+
+    return sect
+
+def read_trax():
+    f = open("trax.dat", "rb")
+    sections = []
+    while True:
+        if (sect := read_section(f)) is None:
+            break
+        sections.append(sect)
+    return sections
+
+if __name__ == "__main__":
+    sections = read_trax()
+    for sect in sections:
+        print(sect)
+

+ 48 - 0
bin/app/script/view.py

@@ -0,0 +1,48 @@
+#!/usr/bin/python
+from traxator import *
+import math
+
+dc_id = 2767617242841734550
+vert_id = 39568
+idx_id = 39569
+
+sections = read_trax()
+for sect in sections:
+    match sect:
+        case PutDrawCall(_, _, dcs, stats):
+            for dc in dcs:
+                if dc.dc_id == dc_id:
+                    print(f"DrawCall {dc_id}")
+                    for (i, instr) in enumerate(dc.instrs):
+                        print(f"  {i}. {instr}")
+                    print()
+        case PutVerts(_, verts, buf, _, _, _):
+            if buf == vert_id:
+                print(f"Vert {vert_id}")
+                print("  n verts:", len(verts))
+                print(verts)
+                print()
+                #for v in verts:
+                #    assert not math.isnan(v.x)
+                #    assert not math.isnan(v.y)
+                #    assert not math.isnan(v.r)
+                #    assert not math.isnan(v.g)
+                #    assert not math.isnan(v.b)
+                #    assert not math.isnan(v.a)
+                #    assert not math.isnan(v.u)
+                #    assert not math.isnan(v.v)
+
+                #    assert not math.isinf(v.x)
+                #    assert not math.isinf(v.y)
+                #    assert not math.isinf(v.r)
+                #    assert not math.isinf(v.g)
+                #    assert not math.isinf(v.b)
+                #    assert not math.isinf(v.a)
+                #    assert not math.isinf(v.u)
+                #    assert not math.isinf(v.v)
+        case PutIdxs(_, idxs, buf, _, _, _):
+            if buf == idx_id:
+                print(f"Idx {idx_id}")
+                print(idxs)
+                print()
+

+ 160 - 18
bin/app/src/gfx/mod.rs

@@ -16,7 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
  */
 
 
-use darkfi_serial::{async_trait, Decodable, Encodable, SerialDecodable, SerialEncodable};
+use darkfi_serial::{
+    async_trait, AsyncEncodable, AsyncWrite, Decodable, Encodable, FutAsyncWriteExt,
+    SerialDecodable, SerialEncodable,
+};
 use log::debug;
 use log::debug;
 use miniquad::{
 use miniquad::{
     conf, window, Backend, Bindings, BlendFactor, BlendState, BlendValue, BufferLayout,
     conf, window, Backend, Bindings, BlendFactor, BlendState, BlendValue, BufferLayout,
@@ -27,6 +30,7 @@ use miniquad::{
 use std::{
 use std::{
     collections::HashMap,
     collections::HashMap,
     fs::File,
     fs::File,
+    io::Write,
     path::PathBuf,
     path::PathBuf,
     sync::{
     sync::{
         atomic::{AtomicU32, Ordering},
         atomic::{AtomicU32, Ordering},
@@ -38,6 +42,8 @@ mod favico;
 mod linalg;
 mod linalg;
 pub use linalg::{Dimension, Point, Rectangle};
 pub use linalg::{Dimension, Point, Rectangle};
 mod shader;
 mod shader;
+mod trax;
+use trax::get_trax;
 
 
 use crate::{
 use crate::{
     error::{Error, Result},
     error::{Error, Result},
@@ -47,6 +53,7 @@ use crate::{
 // This is very noisy so suppress output by default
 // This is very noisy so suppress output by default
 const DEBUG_RENDER: bool = false;
 const DEBUG_RENDER: bool = false;
 const DEBUG_GFXAPI: bool = false;
 const DEBUG_GFXAPI: bool = false;
+const DEBUG_TRAX: bool = false;
 
 
 #[macro_export]
 #[macro_export]
 macro_rules! gfxtag {
 macro_rules! gfxtag {
@@ -173,10 +180,11 @@ impl RenderApi {
         width: u16,
         width: u16,
         height: u16,
         height: u16,
         data: Vec<u8>,
         data: Vec<u8>,
+        tag: DebugTag,
     ) -> (GfxTextureId, EpochIndex) {
     ) -> (GfxTextureId, EpochIndex) {
         let gfx_texture_id = NEXT_TEXTURE_ID.fetch_add(1, Ordering::SeqCst);
         let gfx_texture_id = NEXT_TEXTURE_ID.fetch_add(1, Ordering::SeqCst);
 
 
-        let method = GraphicsMethod::NewTexture((width, height, data, gfx_texture_id));
+        let method = GraphicsMethod::NewTexture((width, height, data, gfx_texture_id, tag));
         let epoch = self.send(method);
         let epoch = self.send(method);
 
 
         (gfx_texture_id, epoch)
         (gfx_texture_id, epoch)
@@ -189,7 +197,7 @@ impl RenderApi {
         data: Vec<u8>,
         data: Vec<u8>,
         tag: DebugTag,
         tag: DebugTag,
     ) -> ManagedTexturePtr {
     ) -> ManagedTexturePtr {
-        let (id, epoch) = self.new_unmanaged_texture(width, height, data);
+        let (id, epoch) = self.new_unmanaged_texture(width, height, data, tag);
         Arc::new(ManagedTexture { id, epoch, render_api: self.clone(), tag })
         Arc::new(ManagedTexture { id, epoch, render_api: self.clone(), tag })
     }
     }
 
 
@@ -198,30 +206,38 @@ impl RenderApi {
         self.send_with_epoch(method, epoch);
         self.send_with_epoch(method, epoch);
     }
     }
 
 
-    fn new_unmanaged_vertex_buffer(&self, verts: Vec<Vertex>) -> (GfxBufferId, EpochIndex) {
+    fn new_unmanaged_vertex_buffer(
+        &self,
+        verts: Vec<Vertex>,
+        tag: DebugTag,
+    ) -> (GfxBufferId, EpochIndex) {
         let gfx_buffer_id = NEXT_BUFFER_ID.fetch_add(1, Ordering::SeqCst);
         let gfx_buffer_id = NEXT_BUFFER_ID.fetch_add(1, Ordering::SeqCst);
 
 
-        let method = GraphicsMethod::NewVertexBuffer((verts, gfx_buffer_id));
+        let method = GraphicsMethod::NewVertexBuffer((verts, gfx_buffer_id, tag));
         let epoch = self.send(method);
         let epoch = self.send(method);
 
 
         (gfx_buffer_id, epoch)
         (gfx_buffer_id, epoch)
     }
     }
 
 
-    fn new_unmanaged_index_buffer(&self, indices: Vec<u16>) -> (GfxBufferId, EpochIndex) {
+    fn new_unmanaged_index_buffer(
+        &self,
+        indices: Vec<u16>,
+        tag: DebugTag,
+    ) -> (GfxBufferId, EpochIndex) {
         let gfx_buffer_id = NEXT_BUFFER_ID.fetch_add(1, Ordering::SeqCst);
         let gfx_buffer_id = NEXT_BUFFER_ID.fetch_add(1, Ordering::SeqCst);
 
 
-        let method = GraphicsMethod::NewIndexBuffer((indices, gfx_buffer_id));
+        let method = GraphicsMethod::NewIndexBuffer((indices, gfx_buffer_id, tag));
         let epoch = self.send(method);
         let epoch = self.send(method);
 
 
         (gfx_buffer_id, epoch)
         (gfx_buffer_id, epoch)
     }
     }
 
 
     pub fn new_vertex_buffer(&self, verts: Vec<Vertex>, tag: DebugTag) -> ManagedBufferPtr {
     pub fn new_vertex_buffer(&self, verts: Vec<Vertex>, tag: DebugTag) -> ManagedBufferPtr {
-        let (id, epoch) = self.new_unmanaged_vertex_buffer(verts);
+        let (id, epoch) = self.new_unmanaged_vertex_buffer(verts, tag);
         Arc::new(ManagedBuffer { id, epoch, render_api: self.clone(), tag, buftype: 0 })
         Arc::new(ManagedBuffer { id, epoch, render_api: self.clone(), tag, buftype: 0 })
     }
     }
     pub fn new_index_buffer(&self, indices: Vec<u16>, tag: DebugTag) -> ManagedBufferPtr {
     pub fn new_index_buffer(&self, indices: Vec<u16>, tag: DebugTag) -> ManagedBufferPtr {
-        let (id, epoch) = self.new_unmanaged_index_buffer(indices);
+        let (id, epoch) = self.new_unmanaged_index_buffer(indices, tag);
         Arc::new(ManagedBuffer { id, epoch, render_api: self.clone(), tag, buftype: 1 })
         Arc::new(ManagedBuffer { id, epoch, render_api: self.clone(), tag, buftype: 1 })
     }
     }
 
 
@@ -311,7 +327,44 @@ impl GfxDrawMesh {
     }
     }
 }
 }
 
 
-#[derive(Debug, Clone)]
+impl Encodable for GfxDrawMesh {
+    fn encode<S: Write>(&self, s: &mut S) -> std::result::Result<usize, std::io::Error> {
+        let mut len = 0;
+        len += self.vertex_buffer.id.encode(s)?;
+        len += self.vertex_buffer.epoch.encode(s)?;
+        len += self.vertex_buffer.tag.encode(s)?;
+        len += self.vertex_buffer.buftype.encode(s)?;
+        len += self.index_buffer.id.encode(s)?;
+        len += self.index_buffer.epoch.encode(s)?;
+        len += self.index_buffer.tag.encode(s)?;
+        len += self.index_buffer.buftype.encode(s)?;
+        match &self.texture {
+            Some(t) => {
+                len += 1u8.encode(s)?;
+                len += t.id.encode(s)?;
+                len += t.epoch.encode(s)?;
+                len += t.tag.encode(s)?;
+            }
+            None => {
+                len += 0u8.encode(s)?;
+            }
+        }
+        len += self.num_elements.encode(s)?;
+        Ok(len)
+    }
+}
+
+#[async_trait]
+impl AsyncEncodable for GfxDrawMesh {
+    async fn encode_async<W: AsyncWrite + Unpin + Send>(
+        &self,
+        _: &mut W,
+    ) -> std::io::Result<usize> {
+        Ok(0)
+    }
+}
+
+#[derive(Debug, Clone, SerialEncodable)]
 pub enum GfxDrawInstruction {
 pub enum GfxDrawInstruction {
     SetScale(f32),
     SetScale(f32),
     Move(Point),
     Move(Point),
@@ -340,7 +393,7 @@ impl GfxDrawInstruction {
     }
     }
 }
 }
 
 
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, SerialEncodable)]
 pub struct GfxDrawCall {
 pub struct GfxDrawCall {
     pub instrs: Vec<GfxDrawInstruction>,
     pub instrs: Vec<GfxDrawInstruction>,
     pub dcs: Vec<u64>,
     pub dcs: Vec<u64>,
@@ -423,6 +476,9 @@ impl<'a> RenderContext<'a> {
         if DEBUG_RENDER {
         if DEBUG_RENDER {
             debug!(target: "gfx", "RenderContext::draw()");
             debug!(target: "gfx", "RenderContext::draw()");
         }
         }
+        if DEBUG_TRAX {
+            get_trax().lock().set_curr(0);
+        }
         self.draw_call(&self.draw_calls[&0], 0, DEBUG_RENDER);
         self.draw_call(&self.draw_calls[&0], 0, DEBUG_RENDER);
         if DEBUG_RENDER {
         if DEBUG_RENDER {
             debug!(target: "gfx", "RenderContext::draw() [DONE]");
             debug!(target: "gfx", "RenderContext::draw() [DONE]");
@@ -474,7 +530,10 @@ impl<'a> RenderContext<'a> {
         let old_view = self.view;
         let old_view = self.view;
         let old_cursor = self.cursor;
         let old_cursor = self.cursor;
 
 
-        for instr in &draw_call.instrs {
+        for (idx, instr) in draw_call.instrs.iter().enumerate() {
+            if DEBUG_TRAX {
+                get_trax().lock().set_instr(idx);
+            }
             match instr {
             match instr {
                 DrawInstruction::SetScale(scale) => {
                 DrawInstruction::SetScale(scale) => {
                     self.scale = *scale;
                     self.scale = *scale;
@@ -557,6 +616,9 @@ impl<'a> RenderContext<'a> {
         draw_calls.sort_unstable_by_key(|(_, dc)| dc.z_index);
         draw_calls.sort_unstable_by_key(|(_, dc)| dc.z_index);
 
 
         for (dc_key, dc) in draw_calls {
         for (dc_key, dc) in draw_calls {
+            if DEBUG_TRAX {
+                get_trax().lock().set_curr(*dc_key);
+            }
             if is_debug {
             if is_debug {
                 debug!(target: "gfx", "{ws}drawcall {dc_key}");
                 debug!(target: "gfx", "{ws}drawcall {dc_key}");
             }
             }
@@ -579,10 +641,10 @@ impl<'a> RenderContext<'a> {
 
 
 #[derive(Clone)]
 #[derive(Clone)]
 pub enum GraphicsMethod {
 pub enum GraphicsMethod {
-    NewTexture((u16, u16, Vec<u8>, GfxTextureId)),
+    NewTexture((u16, u16, Vec<u8>, GfxTextureId, DebugTag)),
     DeleteTexture((GfxTextureId, DebugTag)),
     DeleteTexture((GfxTextureId, DebugTag)),
-    NewVertexBuffer((Vec<Vertex>, GfxBufferId)),
-    NewIndexBuffer((Vec<u16>, GfxBufferId)),
+    NewVertexBuffer((Vec<Vertex>, GfxBufferId, DebugTag)),
+    NewIndexBuffer((Vec<u16>, GfxBufferId, DebugTag)),
     DeleteBuffer((GfxBufferId, DebugTag, u8)),
     DeleteBuffer((GfxBufferId, DebugTag, u8)),
     ReplaceDrawCalls { timest: u64, dcs: Vec<(u64, GfxDrawCall)> },
     ReplaceDrawCalls { timest: u64, dcs: Vec<(u64, GfxDrawCall)> },
 }
 }
@@ -739,6 +801,9 @@ struct Stage {
 
 
 impl Stage {
 impl Stage {
     pub fn new() -> Self {
     pub fn new() -> Self {
+        if DEBUG_TRAX {
+            get_trax().lock().clear();
+        }
         let mut ctx: Box<dyn RenderingBackend> = window::new_rendering_backend();
         let mut ctx: Box<dyn RenderingBackend> = window::new_rendering_backend();
 
 
         let god = GOD.get().unwrap();
         let god = GOD.get().unwrap();
@@ -810,14 +875,14 @@ impl Stage {
     fn process_method(&mut self, mut method: GraphicsMethod) {
     fn process_method(&mut self, mut method: GraphicsMethod) {
         //debug!(target: "gfx", "Received method: {:?}", method);
         //debug!(target: "gfx", "Received method: {:?}", method);
         let res = match &mut method {
         let res = match &mut method {
-            GraphicsMethod::NewTexture((width, height, data, gfx_texture_id)) => {
+            GraphicsMethod::NewTexture((width, height, data, gfx_texture_id, _)) => {
                 self.method_new_texture(*width, *height, data, *gfx_texture_id)
                 self.method_new_texture(*width, *height, data, *gfx_texture_id)
             }
             }
             GraphicsMethod::DeleteTexture((texture, _)) => self.method_delete_texture(*texture),
             GraphicsMethod::DeleteTexture((texture, _)) => self.method_delete_texture(*texture),
-            GraphicsMethod::NewVertexBuffer((verts, gbuffid)) => {
+            GraphicsMethod::NewVertexBuffer((verts, gbuffid, _)) => {
                 self.method_new_vertex_buffer(verts, *gbuffid)
                 self.method_new_vertex_buffer(verts, *gbuffid)
             }
             }
-            GraphicsMethod::NewIndexBuffer((indices, gbuffid)) => {
+            GraphicsMethod::NewIndexBuffer((indices, gbuffid, _)) => {
                 self.method_new_index_buffer(indices, *gbuffid)
                 self.method_new_index_buffer(indices, *gbuffid)
             }
             }
             GraphicsMethod::DeleteBuffer((buffer, _, _)) => self.method_delete_buffer(*buffer),
             GraphicsMethod::DeleteBuffer((buffer, _, _)) => self.method_delete_buffer(*buffer),
@@ -848,13 +913,22 @@ impl Stage {
             //       ansi_texture(width as usize, height as usize, &data));
             //       ansi_texture(width as usize, height as usize, &data));
         }
         }
         if let Some(_) = self.textures.insert(gfx_texture_id, texture) {
         if let Some(_) = self.textures.insert(gfx_texture_id, texture) {
+            if DEBUG_TRAX {
+                get_trax().lock().put_stat(2);
+            }
             //panic!("Duplicate texture ID={gfx_texture_id} detected!");
             //panic!("Duplicate texture ID={gfx_texture_id} detected!");
             return Err(Error::GfxDuplicateTextureID)
             return Err(Error::GfxDuplicateTextureID)
         }
         }
+        if DEBUG_TRAX {
+            get_trax().lock().put_stat(0);
+        }
         Ok(())
         Ok(())
     }
     }
     fn method_delete_texture(&mut self, gfx_texture_id: GfxTextureId) -> Result<()> {
     fn method_delete_texture(&mut self, gfx_texture_id: GfxTextureId) -> Result<()> {
         let Some(texture) = self.textures.remove(&gfx_texture_id) else {
         let Some(texture) = self.textures.remove(&gfx_texture_id) else {
+            if DEBUG_TRAX {
+                get_trax().lock().put_stat(2);
+            }
             //.expect("couldn't find gfx_texture_id");
             //.expect("couldn't find gfx_texture_id");
             return Err(Error::GfxUnknownTextureID)
             return Err(Error::GfxUnknownTextureID)
         };
         };
@@ -863,6 +937,9 @@ impl Stage {
                    gfx_texture_id, texture);
                    gfx_texture_id, texture);
         }
         }
         self.ctx.delete_texture(texture);
         self.ctx.delete_texture(texture);
+        if DEBUG_TRAX {
+            get_trax().lock().put_stat(0);
+        }
         Ok(())
         Ok(())
     }
     }
     fn method_new_vertex_buffer(
     fn method_new_vertex_buffer(
@@ -882,9 +959,15 @@ impl Stage {
             //       verts, gfx_buffer_id, buffer);
             //       verts, gfx_buffer_id, buffer);
         }
         }
         if let Some(_) = self.buffers.insert(gfx_buffer_id, buffer) {
         if let Some(_) = self.buffers.insert(gfx_buffer_id, buffer) {
+            if DEBUG_TRAX {
+                get_trax().lock().put_stat(2);
+            }
             //panic!("Duplicate vertex buffer ID={gfx_buffer_id} detected!");
             //panic!("Duplicate vertex buffer ID={gfx_buffer_id} detected!");
             return Err(Error::GfxDuplicateBufferID)
             return Err(Error::GfxDuplicateBufferID)
         }
         }
+        if DEBUG_TRAX {
+            get_trax().lock().put_stat(0);
+        }
         Ok(())
         Ok(())
     }
     }
     fn method_new_index_buffer(
     fn method_new_index_buffer(
@@ -904,13 +987,22 @@ impl Stage {
             //       indices, gfx_buffer_id, buffer);
             //       indices, gfx_buffer_id, buffer);
         }
         }
         if let Some(_) = self.buffers.insert(gfx_buffer_id, buffer) {
         if let Some(_) = self.buffers.insert(gfx_buffer_id, buffer) {
+            if DEBUG_TRAX {
+                get_trax().lock().put_stat(2);
+            }
             //panic!("Duplicate index buffer ID={gfx_buffer_id} detected!");
             //panic!("Duplicate index buffer ID={gfx_buffer_id} detected!");
             return Err(Error::GfxDuplicateBufferID)
             return Err(Error::GfxDuplicateBufferID)
         }
         }
+        if DEBUG_TRAX {
+            get_trax().lock().put_stat(0);
+        }
         Ok(())
         Ok(())
     }
     }
     fn method_delete_buffer(&mut self, gfx_buffer_id: GfxBufferId) -> Result<()> {
     fn method_delete_buffer(&mut self, gfx_buffer_id: GfxBufferId) -> Result<()> {
         let Some(buffer) = self.buffers.remove(&gfx_buffer_id) else {
         let Some(buffer) = self.buffers.remove(&gfx_buffer_id) else {
+            if DEBUG_TRAX {
+                get_trax().lock().put_stat(2);
+            }
             //.expect("couldn't find gfx_buffer_id");
             //.expect("couldn't find gfx_buffer_id");
             return Err(Error::GfxUnknownBufferID)
             return Err(Error::GfxUnknownBufferID)
         };
         };
@@ -919,6 +1011,9 @@ impl Stage {
                    gfx_buffer_id, buffer);
                    gfx_buffer_id, buffer);
         }
         }
         self.ctx.delete_buffer(buffer);
         self.ctx.delete_buffer(buffer);
+        if DEBUG_TRAX {
+            get_trax().lock().put_stat(0);
+        }
         Ok(())
         Ok(())
     }
     }
     fn method_replace_draw_calls(
     fn method_replace_draw_calls(
@@ -931,6 +1026,9 @@ impl Stage {
         }
         }
         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 {
+                    get_trax().lock().put_stat(3);
+                }
                 error!(target: "gfx", "fatal: replace_draw_calls({timest}, ...) failed with item ID={key}");
                 error!(target: "gfx", "fatal: replace_draw_calls({timest}, ...) failed with item ID={key}");
                 continue
                 continue
             };
             };
@@ -939,31 +1037,75 @@ impl Stage {
                 Some(old_val) => {
                 Some(old_val) => {
                     // Only replace the draw call if it is more recent
                     // Only replace the draw call if it is more recent
                     if old_val.timest < timest {
                     if old_val.timest < timest {
+                        if DEBUG_TRAX {
+                            get_trax().lock().put_stat(0);
+                        }
                         *old_val = val;
                         *old_val = val;
                     } else {
                     } else {
                         trace!(target: "gfx", "Rejected stale draw_call {key}: {val:?}");
                         trace!(target: "gfx", "Rejected stale draw_call {key}: {val:?}");
+                        if DEBUG_TRAX {
+                            get_trax().lock().put_stat(2);
+                        }
                     }
                     }
                 }
                 }
                 None => {
                 None => {
                     self.draw_calls.insert(key, val);
                     self.draw_calls.insert(key, val);
+                    if DEBUG_TRAX {
+                        get_trax().lock().put_stat(1);
+                    }
                 }
                 }
             }
             }
         }
         }
         Ok(())
         Ok(())
     }
     }
+
+    fn trax_method(&self, epoch: EpochIndex, method: &GraphicsMethod) {
+        let mut trax = get_trax().lock();
+        match method {
+            GraphicsMethod::NewTexture((_, _, _, gfx_texture_id, tag)) => {
+                trax.put_tex(epoch, *gfx_texture_id, *tag);
+            }
+            GraphicsMethod::DeleteTexture((texture, tag)) => {
+                trax.del_tex(epoch, *texture, *tag);
+            }
+            GraphicsMethod::NewVertexBuffer((verts, gbuffid, tag)) => {
+                trax.put_verts(epoch, verts.clone(), *gbuffid, *tag, 0);
+            }
+            GraphicsMethod::NewIndexBuffer((idxs, gbuffid, tag)) => {
+                trax.put_idxs(epoch, idxs.clone(), *gbuffid, *tag, 1);
+            }
+            GraphicsMethod::DeleteBuffer((buffer, tag, buftype)) => {
+                trax.del_buf(epoch, *buffer, *tag, *buftype);
+            }
+            GraphicsMethod::ReplaceDrawCalls { timest, dcs } => {
+                trax.put_dcs(epoch, *timest, dcs);
+            }
+        };
+    }
 }
 }
 
 
 impl EventHandler for Stage {
 impl EventHandler for Stage {
     fn update(&mut self) {
     fn update(&mut self) {
         // Process as many methods as we can
         // Process as many methods as we can
         while let Ok((epoch, method)) = self.method_rep.try_recv() {
         while let Ok((epoch, method)) = self.method_rep.try_recv() {
+            if DEBUG_TRAX {
+                self.trax_method(epoch, &method);
+            }
             if epoch < self.epoch {
             if epoch < self.epoch {
+                if DEBUG_TRAX {
+                    let mut trax = get_trax().lock();
+                    trax.put_stat(1);
+                    trax.flush();
+                }
                 // Discard old rubbish
                 // Discard old rubbish
                 trace!(target: "gfx", "Discard method with old epoch: {epoch} curr: {} [method={method:?}]", self.epoch);
                 trace!(target: "gfx", "Discard method with old epoch: {epoch} curr: {} [method={method:?}]", self.epoch);
                 continue
                 continue
             }
             }
             assert_eq!(epoch, self.epoch);
             assert_eq!(epoch, self.epoch);
             self.process_method(method);
             self.process_method(method);
+            if DEBUG_TRAX {
+                get_trax().lock().flush();
+            }
         }
         }
     }
     }
 
 

+ 143 - 0
bin/app/src/gfx/trax.rs

@@ -0,0 +1,143 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use darkfi_serial::Encodable;
+use log::debug;
+use parking_lot::Mutex as SyncMutex;
+use std::{fs::File, io::Write, sync::OnceLock};
+
+use super::{DebugTag, GfxBufferId, GfxDrawCall, GfxTextureId, Vertex};
+use crate::EpochIndex;
+
+macro_rules! d { ($($arg:tt)*) => { debug!(target: "gfx::trax", $($arg)*); } }
+
+pub struct Trax {
+    file: File,
+    buf: Vec<u8>,
+}
+
+impl Trax {
+    fn new() -> Self {
+        let path = crate::android::get_external_storage_path().join("trax.dat");
+        let file = File::create(path).unwrap();
+        Self { file, buf: vec![] }
+    }
+
+    pub fn clear(&mut self) {
+        d!("clear");
+        self.file.set_len(0).unwrap();
+    }
+
+    pub fn put_dcs(&mut self, epoch: EpochIndex, timest: u64, dcs: &Vec<(u64, GfxDrawCall)>) {
+        d!("put_dcs({epoch}, {timest}, {dcs:?})");
+        0u8.encode(&mut self.buf).unwrap();
+        epoch.encode(&mut self.buf).unwrap();
+        timest.encode(&mut self.buf).unwrap();
+        dcs.encode(&mut self.buf).unwrap();
+    }
+
+    pub fn put_tex(&mut self, epoch: EpochIndex, tex: GfxTextureId, tag: DebugTag) {
+        d!("put_tex({epoch}, {tex}, {tag:?})");
+        1u8.encode(&mut self.buf).unwrap();
+        epoch.encode(&mut self.buf).unwrap();
+        tex.encode(&mut self.buf).unwrap();
+        tag.encode(&mut self.buf).unwrap();
+    }
+    pub fn put_verts(
+        &mut self,
+        epoch: EpochIndex,
+        verts: Vec<Vertex>,
+        buf: GfxBufferId,
+        tag: DebugTag,
+        buftype: u8,
+    ) {
+        d!("put_verts({epoch}, ..., {buf}, {tag:?}, {buftype})");
+        2u8.encode(&mut self.buf).unwrap();
+        epoch.encode(&mut self.buf).unwrap();
+        verts.encode(&mut self.buf).unwrap();
+        buf.encode(&mut self.buf).unwrap();
+        tag.encode(&mut self.buf).unwrap();
+        buftype.encode(&mut self.buf).unwrap();
+    }
+    pub fn put_idxs(
+        &mut self,
+        epoch: EpochIndex,
+        idxs: Vec<u16>,
+        buf: GfxBufferId,
+        tag: DebugTag,
+        buftype: u8,
+    ) {
+        d!("put_idxs({epoch}, ..., {buf}, {tag:?}, {buftype})");
+        3u8.encode(&mut self.buf).unwrap();
+        epoch.encode(&mut self.buf).unwrap();
+        idxs.encode(&mut self.buf).unwrap();
+        buf.encode(&mut self.buf).unwrap();
+        tag.encode(&mut self.buf).unwrap();
+        buftype.encode(&mut self.buf).unwrap();
+    }
+
+    pub fn put_stat(&mut self, code: u8) {
+        d!("put_stat({code})");
+        code.encode(&mut self.buf).unwrap();
+    }
+
+    pub fn del_tex(&mut self, epoch: EpochIndex, tex: GfxTextureId, tag: DebugTag) {
+        d!("del_tex({epoch}, {tex}, {tag:?})");
+        4u8.encode(&mut self.buf).unwrap();
+        epoch.encode(&mut self.buf).unwrap();
+        tex.encode(&mut self.buf).unwrap();
+        tag.encode(&mut self.buf).unwrap();
+    }
+    pub fn del_buf(&mut self, epoch: EpochIndex, buf: GfxBufferId, tag: DebugTag, buftype: u8) {
+        d!("del_buf({epoch}, {buf}, {tag:?}, {buftype})");
+        5u8.encode(&mut self.buf).unwrap();
+        epoch.encode(&mut self.buf).unwrap();
+        buf.encode(&mut self.buf).unwrap();
+        tag.encode(&mut self.buf).unwrap();
+        buftype.encode(&mut self.buf).unwrap();
+    }
+
+    pub fn set_curr(&mut self, dc: u64) {
+        d!("set_curr({dc})");
+        6u8.encode(&mut self.buf).unwrap();
+        dc.encode(&mut self.buf).unwrap();
+        self.flush();
+    }
+    pub fn set_instr(&mut self, idx: usize) {
+        d!("set_instr({idx})");
+        7u8.encode(&mut self.buf).unwrap();
+        idx.encode(&mut self.buf).unwrap();
+        self.flush();
+    }
+
+    pub fn flush(&mut self) {
+        d!("flush");
+        let buf = std::mem::take(&mut self.buf);
+        if buf.is_empty() {
+            d!(" -> skipping flush");
+            return
+        }
+        buf.encode(&mut self.file).unwrap();
+    }
+}
+
+static TRAX: OnceLock<SyncMutex<Trax>> = OnceLock::new();
+
+pub(super) fn get_trax() -> &'static SyncMutex<Trax> {
+    TRAX.get_or_init(|| SyncMutex::new(Trax::new()))
+}

+ 5 - 9
bin/app/src/ui/vector_art/mod.rs

@@ -108,17 +108,13 @@ impl VectorArt {
 
 
         let rect = self.rect.get();
         let rect = self.rect.get();
         let verts = self.shape.eval(rect.w, rect.h).expect("bad shape");
         let verts = self.shape.eval(rect.w, rect.h).expect("bad shape");
+        let indices = self.shape.indices.clone();
+        let num_elements = self.shape.indices.len() as i32;
 
 
-        //debug!(target: "ui::vector_art", "=> {verts:#?}");
+        //debug!(target: "ui::vector_art", "vec_draw_instrs {verts:?} | {indices:?} | {num_elements}");
         let vertex_buffer = self.render_api.new_vertex_buffer(verts, gfxtag!("vectorart"));
         let vertex_buffer = self.render_api.new_vertex_buffer(verts, gfxtag!("vectorart"));
-        let index_buffer =
-            self.render_api.new_index_buffer(self.shape.indices.clone(), gfxtag!("vectorart"));
-        let mesh = GfxDrawMesh {
-            vertex_buffer,
-            index_buffer,
-            texture: None,
-            num_elements: self.shape.indices.len() as i32,
-        };
+        let index_buffer = self.render_api.new_index_buffer(indices, gfxtag!("vectorart"));
+        let mesh = GfxDrawMesh { vertex_buffer, index_buffer, texture: None, num_elements };
 
 
         vec![GfxDrawInstruction::Move(rect.pos()), GfxDrawInstruction::Draw(mesh)]
         vec![GfxDrawInstruction::Move(rect.pos()), GfxDrawInstruction::Draw(mesh)]
     }
     }