diff --git a/bin/app/pydrk/serial.py b/bin/app/pydrk/serial.py
index 456547290..04a588091 100644
--- a/bin/app/pydrk/serial.py
+++ b/bin/app/pydrk/serial.py
@@ -61,6 +61,12 @@ class Cursor:
raise Exception("invalid read")
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):
b = cur.read(1)
return int.from_bytes(b, "little")
@@ -81,6 +87,10 @@ def read_f32(cur):
by = cur.read(4)
return struct.unpack(".
*/
-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 miniquad::{
conf, window, Backend, Bindings, BlendFactor, BlendState, BlendValue, BufferLayout,
@@ -27,6 +30,7 @@ use miniquad::{
use std::{
collections::HashMap,
fs::File,
+ io::Write,
path::PathBuf,
sync::{
atomic::{AtomicU32, Ordering},
@@ -38,6 +42,8 @@ mod favico;
mod linalg;
pub use linalg::{Dimension, Point, Rectangle};
mod shader;
+mod trax;
+use trax::get_trax;
use crate::{
error::{Error, Result},
@@ -47,6 +53,7 @@ use crate::{
// This is very noisy so suppress output by default
const DEBUG_RENDER: bool = false;
const DEBUG_GFXAPI: bool = false;
+const DEBUG_TRAX: bool = false;
#[macro_export]
macro_rules! gfxtag {
@@ -173,10 +180,11 @@ impl RenderApi {
width: u16,
height: u16,
data: Vec,
+ tag: DebugTag,
) -> (GfxTextureId, EpochIndex) {
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);
(gfx_texture_id, epoch)
@@ -189,7 +197,7 @@ impl RenderApi {
data: Vec,
tag: DebugTag,
) -> 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 })
}
@@ -198,30 +206,38 @@ impl RenderApi {
self.send_with_epoch(method, epoch);
}
- fn new_unmanaged_vertex_buffer(&self, verts: Vec) -> (GfxBufferId, EpochIndex) {
+ fn new_unmanaged_vertex_buffer(
+ &self,
+ verts: Vec,
+ tag: DebugTag,
+ ) -> (GfxBufferId, EpochIndex) {
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);
(gfx_buffer_id, epoch)
}
- fn new_unmanaged_index_buffer(&self, indices: Vec) -> (GfxBufferId, EpochIndex) {
+ fn new_unmanaged_index_buffer(
+ &self,
+ indices: Vec,
+ tag: DebugTag,
+ ) -> (GfxBufferId, EpochIndex) {
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);
(gfx_buffer_id, epoch)
}
pub fn new_vertex_buffer(&self, verts: Vec, 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 })
}
pub fn new_index_buffer(&self, indices: Vec, 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 })
}
@@ -311,7 +327,44 @@ impl GfxDrawMesh {
}
}
-#[derive(Debug, Clone)]
+impl Encodable for GfxDrawMesh {
+ fn encode(&self, s: &mut S) -> std::result::Result {
+ 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(
+ &self,
+ _: &mut W,
+ ) -> std::io::Result {
+ Ok(0)
+ }
+}
+
+#[derive(Debug, Clone, SerialEncodable)]
pub enum GfxDrawInstruction {
SetScale(f32),
Move(Point),
@@ -340,7 +393,7 @@ impl GfxDrawInstruction {
}
}
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, SerialEncodable)]
pub struct GfxDrawCall {
pub instrs: Vec,
pub dcs: Vec,
@@ -423,6 +476,9 @@ impl<'a> RenderContext<'a> {
if DEBUG_RENDER {
debug!(target: "gfx", "RenderContext::draw()");
}
+ if DEBUG_TRAX {
+ get_trax().lock().set_curr(0);
+ }
self.draw_call(&self.draw_calls[&0], 0, DEBUG_RENDER);
if DEBUG_RENDER {
debug!(target: "gfx", "RenderContext::draw() [DONE]");
@@ -474,7 +530,10 @@ impl<'a> RenderContext<'a> {
let old_view = self.view;
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 {
DrawInstruction::SetScale(scale) => {
self.scale = *scale;
@@ -557,6 +616,9 @@ impl<'a> RenderContext<'a> {
draw_calls.sort_unstable_by_key(|(_, dc)| dc.z_index);
for (dc_key, dc) in draw_calls {
+ if DEBUG_TRAX {
+ get_trax().lock().set_curr(*dc_key);
+ }
if is_debug {
debug!(target: "gfx", "{ws}drawcall {dc_key}");
}
@@ -579,10 +641,10 @@ impl<'a> RenderContext<'a> {
#[derive(Clone)]
pub enum GraphicsMethod {
- NewTexture((u16, u16, Vec, GfxTextureId)),
+ NewTexture((u16, u16, Vec, GfxTextureId, DebugTag)),
DeleteTexture((GfxTextureId, DebugTag)),
- NewVertexBuffer((Vec, GfxBufferId)),
- NewIndexBuffer((Vec, GfxBufferId)),
+ NewVertexBuffer((Vec, GfxBufferId, DebugTag)),
+ NewIndexBuffer((Vec, GfxBufferId, DebugTag)),
DeleteBuffer((GfxBufferId, DebugTag, u8)),
ReplaceDrawCalls { timest: u64, dcs: Vec<(u64, GfxDrawCall)> },
}
@@ -739,6 +801,9 @@ struct Stage {
impl Stage {
pub fn new() -> Self {
+ if DEBUG_TRAX {
+ get_trax().lock().clear();
+ }
let mut ctx: Box = window::new_rendering_backend();
let god = GOD.get().unwrap();
@@ -810,14 +875,14 @@ impl Stage {
fn process_method(&mut self, mut method: GraphicsMethod) {
//debug!(target: "gfx", "Received method: {:?}", 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)
}
GraphicsMethod::DeleteTexture((texture, _)) => self.method_delete_texture(*texture),
- GraphicsMethod::NewVertexBuffer((verts, gbuffid)) => {
+ GraphicsMethod::NewVertexBuffer((verts, gbuffid, _)) => {
self.method_new_vertex_buffer(verts, *gbuffid)
}
- GraphicsMethod::NewIndexBuffer((indices, gbuffid)) => {
+ GraphicsMethod::NewIndexBuffer((indices, gbuffid, _)) => {
self.method_new_index_buffer(indices, *gbuffid)
}
GraphicsMethod::DeleteBuffer((buffer, _, _)) => self.method_delete_buffer(*buffer),
@@ -848,13 +913,22 @@ impl Stage {
// ansi_texture(width as usize, height as usize, &data));
}
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!");
return Err(Error::GfxDuplicateTextureID)
}
+ if DEBUG_TRAX {
+ get_trax().lock().put_stat(0);
+ }
Ok(())
}
fn method_delete_texture(&mut self, gfx_texture_id: GfxTextureId) -> Result<()> {
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");
return Err(Error::GfxUnknownTextureID)
};
@@ -863,6 +937,9 @@ impl Stage {
gfx_texture_id, texture);
}
self.ctx.delete_texture(texture);
+ if DEBUG_TRAX {
+ get_trax().lock().put_stat(0);
+ }
Ok(())
}
fn method_new_vertex_buffer(
@@ -882,9 +959,15 @@ impl Stage {
// verts, 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!");
return Err(Error::GfxDuplicateBufferID)
}
+ if DEBUG_TRAX {
+ get_trax().lock().put_stat(0);
+ }
Ok(())
}
fn method_new_index_buffer(
@@ -904,13 +987,22 @@ impl Stage {
// indices, 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!");
return Err(Error::GfxDuplicateBufferID)
}
+ if DEBUG_TRAX {
+ get_trax().lock().put_stat(0);
+ }
Ok(())
}
fn method_delete_buffer(&mut self, gfx_buffer_id: GfxBufferId) -> Result<()> {
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");
return Err(Error::GfxUnknownBufferID)
};
@@ -919,6 +1011,9 @@ impl Stage {
gfx_buffer_id, buffer);
}
self.ctx.delete_buffer(buffer);
+ if DEBUG_TRAX {
+ get_trax().lock().put_stat(0);
+ }
Ok(())
}
fn method_replace_draw_calls(
@@ -931,6 +1026,9 @@ impl Stage {
}
for (key, val) in dcs {
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}");
continue
};
@@ -939,31 +1037,75 @@ impl Stage {
Some(old_val) => {
// Only replace the draw call if it is more recent
if old_val.timest < timest {
+ if DEBUG_TRAX {
+ get_trax().lock().put_stat(0);
+ }
*old_val = val;
} else {
trace!(target: "gfx", "Rejected stale draw_call {key}: {val:?}");
+ if DEBUG_TRAX {
+ get_trax().lock().put_stat(2);
+ }
}
}
None => {
self.draw_calls.insert(key, val);
+ if DEBUG_TRAX {
+ get_trax().lock().put_stat(1);
+ }
}
}
}
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 {
fn update(&mut self) {
// Process as many methods as we can
while let Ok((epoch, method)) = self.method_rep.try_recv() {
+ 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();
+ }
}
}
diff --git a/bin/app/src/gfx/trax.rs b/bin/app/src/gfx/trax.rs
new file mode 100644
index 000000000..0201c6b0f
--- /dev/null
+++ b/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 .
+ */
+
+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,
+}
+
+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,
+ 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,
+ 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> = OnceLock::new();
+
+pub(super) fn get_trax() -> &'static SyncMutex {
+ TRAX.get_or_init(|| SyncMutex::new(Trax::new()))
+}
diff --git a/bin/app/src/ui/vector_art/mod.rs b/bin/app/src/ui/vector_art/mod.rs
index 7676fb073..8e0a6b325 100644
--- a/bin/app/src/ui/vector_art/mod.rs
+++ b/bin/app/src/ui/vector_art/mod.rs
@@ -108,17 +108,13 @@ impl VectorArt {
let rect = self.rect.get();
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 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)]
}