add no gfx compile flag
This commit is contained in:
parent
4918c152b5
commit
1f3ddce98f
10 changed files with 356 additions and 349 deletions
8
Makefile
8
Makefile
|
@ -53,3 +53,11 @@ asan: $(ROOT_IFACE_DBG) build/imgui.o
|
|||
|
||||
ubsan: $(ROOT_IFACE_DBG) build/imgui.o
|
||||
$(MOLD) $(CXX) -DDEBUG -g -O0 $(CFLAGS) -fsanitize=undefined $(INC) $(ROOTFLAGS) -o rntviewer src/rntviewer.cpp build/imgui.o $(ROOT_IFACE_DBG) $(LIBS) $(ROOTLIBS)
|
||||
|
||||
# no gfx debug build
|
||||
nogfx_d: $(ROOT_IFACE_DBG)
|
||||
$(MOLD) $(CXX) -DDEBUG -g -O0 -DRNT_NO_GFX -DENABLE_ASAN $(CFLAGS) $(INC) $(ROOTFLAGS) -o rntviewer src/rntviewer.cpp -lasan $(ROOT_IFACE_DBG) $(ROOTLIBS)
|
||||
|
||||
# no gfx release build
|
||||
nogfx_r: $(ROOT_IFACE)
|
||||
$(MOLD) $(CXX) -O2 -DRNT_NO_GFX -DNDEBUG $(CFLAGS) $(INC) $(ROOTFLAGS) -o rntviewer src/rntviewer.cpp $(ROOT_IFACE) $(ROOTLIBS)
|
||||
|
|
20
imgui.ini
20
imgui.ini
|
@ -1,20 +0,0 @@
|
|||
[Window][Debug##Default]
|
||||
Pos=60,60
|
||||
Size=400,400
|
||||
|
||||
[Window][test]
|
||||
Pos=0,0
|
||||
Size=1152,1414
|
||||
|
||||
[Window][main]
|
||||
Pos=0,0
|
||||
Size=1282,1414
|
||||
|
||||
[Window][Hex View]
|
||||
Pos=91,62
|
||||
Size=1002,939
|
||||
|
||||
[Table][0x9FEC56E3,2]
|
||||
Column 0 Weight=1.6105
|
||||
Column 1 Weight=0.3895
|
||||
|
|
@ -23,10 +23,14 @@ struct App_State {
|
|||
User_Input user_input;
|
||||
RNTuple_Data rndata;
|
||||
TFile_Data tfile_data;
|
||||
Viewer viewer;
|
||||
Inspected_File inspected_file;
|
||||
|
||||
#ifndef RNT_NO_GFX
|
||||
Viewer viewer;
|
||||
#endif
|
||||
|
||||
String8 ntpl_name;
|
||||
u64 base_display_addr;
|
||||
|
||||
Delta_Time_Accum delta_time_accum;
|
||||
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
internal
|
||||
b8 init_imgui(GLFWwindow* window) {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void) io;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 400");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal
|
||||
void glfw_error_callback(i32 error, const char *description) {
|
||||
fprintf(stderr, "GLFW error %d: %s\n", error, description);
|
||||
|
|
319
src/render.cpp
319
src/render.cpp
|
@ -1,19 +1,3 @@
|
|||
internal
|
||||
Byte_Range get_section_range(const App_State &app, Section_Id sec)
|
||||
{
|
||||
switch (sec) {
|
||||
default: return { 0, 0 };
|
||||
case Sec_TFile_Header: return Byte_Range { 0, app.tfile_data.root_file_header_size };
|
||||
case Sec_TFile_Object: return app.tfile_data.rng_root_file_obj;
|
||||
case Sec_TFile_Info: return app.tfile_data.rng_root_file_info;
|
||||
case Sec_TFile_FreeList: return app.tfile_data.rng_root_file_free;
|
||||
case Sec_TKey_List: return app.rndata.rng_tkeys_list;
|
||||
case Sec_RNTuple_Anchor: return app.rndata.rng_anchor;
|
||||
case Sec_RNTuple_Header: return app.rndata.rng_header;
|
||||
case Sec_RNTuple_Footer: return app.rndata.rng_footer;
|
||||
}
|
||||
}
|
||||
|
||||
internal
|
||||
void accum_dt_ms(Delta_Time_Accum &accum, f32 dt)
|
||||
{
|
||||
|
@ -37,96 +21,6 @@ f32 calc_avg_dt_ms(const Delta_Time_Accum &accum)
|
|||
return res;
|
||||
}
|
||||
|
||||
internal
|
||||
String8 to_pretty_size(Arena *arena, u64 bytes)
|
||||
{
|
||||
if (bytes >= GiB(1)) return push_str8f(arena, "%.1f GiB", (f32)bytes / GiB(1));
|
||||
if (bytes >= MiB(1)) return push_str8f(arena, "%.1f MiB", (f32)bytes / MiB(1));
|
||||
if (bytes >= KiB(1)) return push_str8f(arena, "%.1f KiB", (f32)bytes / KiB(1));
|
||||
return push_str8f(arena, "%zu B", bytes);
|
||||
}
|
||||
|
||||
internal
|
||||
Section find_section(App_State &app, u64 off)
|
||||
{
|
||||
const RNTuple_Data &rdata = app.rndata;
|
||||
const TFile_Data &tdata = app.tfile_data;
|
||||
u64 rblob_sz = rdata.rblob_header_size; // @Incomplete
|
||||
i64 hilite_cluster = app.viewer.highlight_cluster ? app.viewer.highlighted_cluster : -1;
|
||||
b8 hilite = false;
|
||||
|
||||
// TFile start
|
||||
if (off <= tdata.root_file_header_size) return { Sec_TFile_Header, { 0, tdata.root_file_header_size }, 0, hilite };
|
||||
if (tdata.rng_root_file_obj.start <= off && off < tdata.rng_root_file_obj.end()) return { Sec_TFile_Object, tdata.rng_root_file_obj, 0, hilite };
|
||||
if (tdata.rng_root_file_info.start <= off && off < tdata.rng_root_file_info.end()) return { Sec_TFile_Info, tdata.rng_root_file_info, 0, hilite };
|
||||
if (tdata.rng_root_file_free.start <= off && off < tdata.rng_root_file_free.end()) return { Sec_TFile_FreeList, tdata.rng_root_file_free, 0, hilite };
|
||||
|
||||
/// Handle pages
|
||||
{
|
||||
// fast case: `off` is in the same page info as previous `off`.
|
||||
if (app.last_pinfo->range.start < off && off < app.last_pinfo->range.end()) {
|
||||
hilite = hilite_cluster >= 0 && app.last_pinfo->cluster_id == (u64)hilite_cluster;
|
||||
return { Sec_Page, app.last_pinfo->range, app.last_pinfo->checksum_size(), hilite };
|
||||
}
|
||||
|
||||
// still fast case: `off is in the next page info as the previous.
|
||||
if (app.last_pinfo->next) // don't check if it's checksum, since it's the first byte of the page
|
||||
app.last_pinfo = app.last_pinfo->next;
|
||||
if (app.last_pinfo && app.last_pinfo->range.start <= off && off < app.last_pinfo->range.end()) {
|
||||
hilite = hilite_cluster >= 0 && app.last_pinfo->cluster_id == (u64)hilite_cluster;
|
||||
return { Sec_Page, app.last_pinfo->range, app.last_pinfo->checksum_size(), hilite };
|
||||
}
|
||||
}
|
||||
|
||||
if (rdata.rng_anchor_key.start <= off && off < rdata.rng_anchor.end())
|
||||
return { Sec_RNTuple_Anchor, rdata.rng_anchor, 8, hilite };
|
||||
|
||||
if (rdata.rng_header.start - rblob_sz <= off && off < rdata.rng_header.end())
|
||||
return { Sec_RNTuple_Header, rdata.rng_header, 8, hilite };
|
||||
|
||||
if (rdata.rng_footer.start - rblob_sz <= off && off < rdata.rng_footer.end())
|
||||
return { Sec_RNTuple_Footer, rdata.rng_footer, 8, hilite };
|
||||
|
||||
if (rdata.rng_tkeys_list.start <= off && off < rdata.rng_tkeys_list.end())
|
||||
return { Sec_TKey_List, rdata.rng_tkeys_list, 0, hilite };
|
||||
|
||||
// @Speed
|
||||
for (u64 cg_idx = 0; cg_idx < rdata.n_cluster_groups; ++cg_idx) {
|
||||
Cluster_Group_Info &cg_info = rdata.cluster_groups[cg_idx];
|
||||
if (cg_info.rng_page_list.start - rblob_sz <= off && off < cg_info.rng_page_list.end())
|
||||
return { Sec_Page_List, cg_info.rng_page_list, 8, hilite };
|
||||
}
|
||||
|
||||
// Slow page group lookup, ideally only done once per render when last_pinfo is invalid.
|
||||
for (Page_Info_Chunk *chunk = rdata.page_chunks; chunk; chunk = chunk->next) {
|
||||
// If we're at the start of a chunk, return a fake Sec_Page used to highlight the RBlob header bytes.
|
||||
if (chunk->range.start - rblob_sz <= off && off < chunk->range.start)
|
||||
return { Sec_Page, { chunk->range.start, 0 }, 0, hilite };
|
||||
|
||||
if (chunk->range.start <= off && off < chunk->range.end()) {
|
||||
for (u64 group_idx = chunk->first_group; group_idx < rdata.n_page_groups; ++group_idx) {
|
||||
const Page_Info_Group &group = rdata.page_groups[group_idx];
|
||||
if (off < group.range.start || off >= group.range.end())
|
||||
continue;
|
||||
|
||||
for (Page_Info_Node *pinfo = group.first; pinfo; pinfo = pinfo->next) {
|
||||
if (pinfo->range.start <= off && off < pinfo->range.end()) {
|
||||
app.last_pinfo = pinfo;
|
||||
hilite = hilite_cluster >= 0 && pinfo->cluster_id == (u64)hilite_cluster;
|
||||
return { Sec_Page, pinfo->range, pinfo->checksum_size(), hilite };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Offset 0x%lX is in chunk 0x%lX - 0x%lX, but found in no page_info range!\n",
|
||||
off, chunk->range.start, chunk->range.end());
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
internal
|
||||
ImColor imcol(const f32 col[3])
|
||||
{
|
||||
|
@ -139,7 +33,8 @@ u32 mem_edit_bg_color_fn(const u8 *, u64 off, void *user_data)
|
|||
App_State *app = reinterpret_cast<App_State *>(user_data);
|
||||
off += app->viewer.base_display_addr;
|
||||
|
||||
Section section = find_section(*app, off);
|
||||
i64 hilite_cluster = app->viewer.highlight_cluster ? app->viewer.highlighted_cluster : -1;
|
||||
Section section = find_section(*app, off, hilite_cluster);
|
||||
|
||||
if (section.highlighted) return imcol(app->viewer.col_highlight);
|
||||
if (section.id == Sec_Page && off == section.range.start) return imcol(app->viewer.col_page_start);
|
||||
|
@ -198,10 +93,10 @@ void make_viewer(App_State &app, u16 n_cols)
|
|||
}
|
||||
|
||||
internal
|
||||
void viewer_jump_to(Viewer &viewer, u64 addr)
|
||||
void viewer_jump_to(App_State &app, u64 addr)
|
||||
{
|
||||
viewer.base_display_addr = addr;
|
||||
viewer.mem_edit.GotoAddr = 0;
|
||||
app.base_display_addr = addr;
|
||||
app.viewer.mem_edit.GotoAddr = 0;
|
||||
}
|
||||
|
||||
internal
|
||||
|
@ -218,7 +113,7 @@ void viewer_jump_to_page(App_State &app, u64 page_idx)
|
|||
}
|
||||
|
||||
app.viewer.latest_page_gone_to = page_idx;
|
||||
viewer_jump_to(app.viewer, page->range.start);
|
||||
viewer_jump_to(app, page->range.start);
|
||||
}
|
||||
|
||||
internal
|
||||
|
@ -230,7 +125,7 @@ void viewer_jump_to_page_list(App_State &app, u64 page_list_idx)
|
|||
Cluster_Group_Info &cg_info = app.rndata.cluster_groups[page_list_idx];
|
||||
|
||||
app.viewer.latest_page_list_gone_to = page_list_idx;
|
||||
viewer_jump_to(app.viewer, cg_info.rng_page_list.start);
|
||||
viewer_jump_to(app, cg_info.rng_page_list.start);
|
||||
}
|
||||
|
||||
internal
|
||||
|
@ -250,7 +145,7 @@ void viewer_jump_to_cluster(App_State &app, u64 cluster_idx)
|
|||
}
|
||||
|
||||
app.viewer.highlighted_cluster = cluster_idx;
|
||||
viewer_jump_to(app.viewer, page->range.start);
|
||||
viewer_jump_to(app, page->range.start);
|
||||
}
|
||||
|
||||
internal
|
||||
|
@ -318,7 +213,7 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
|
|||
ImGui::ColorEdit3(col_label.c(), app.viewer.col_section[i], flags);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(sec_name.c()))
|
||||
viewer_jump_to(app.viewer, range.start);
|
||||
viewer_jump_to(app, range.start);
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%s", to_pretty_size(scratch.arena, range.len).c());
|
||||
}
|
||||
|
@ -393,199 +288,3 @@ void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
|
|||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
internal
|
||||
Term_Viewer make_term_viewer()
|
||||
{
|
||||
Term_Viewer viewer {};
|
||||
viewer.col_key = ACol_Green;
|
||||
viewer.col_page_start = ACol_Bright_Magenta;
|
||||
viewer.col_checksum = ACol_Yellow;
|
||||
viewer.col_highlight = ACol_Bright_White;
|
||||
viewer.col_section[Sec_RNTuple_Anchor] = ACol_Bright_Yellow;
|
||||
viewer.col_section[Sec_RNTuple_Header] = ACol_Cyan;
|
||||
viewer.col_section[Sec_RNTuple_Footer] = ACol_Blue;
|
||||
viewer.col_section[Sec_TFile_Header] = ACol_White;
|
||||
viewer.col_section[Sec_TFile_Object] = ACol_Grey;
|
||||
viewer.col_section[Sec_TFile_Info] = ACol_Red;
|
||||
viewer.col_section[Sec_TFile_FreeList] = ACol_Bright_Yellow;
|
||||
viewer.col_section[Sec_Page] = ACol_Magenta;
|
||||
viewer.col_section[Sec_Page_List] = ACol_Bright_Cyan;
|
||||
viewer.col_section[Sec_TKey_List] = ACol_Bright_Green;
|
||||
return viewer;
|
||||
}
|
||||
|
||||
internal
|
||||
Ansi_Color viewer_to_ansi_color(const Viewer &viewer, const Term_Viewer &tviewer, u32 col)
|
||||
{
|
||||
if (col == imcol(viewer.col_key)) return tviewer.col_key;
|
||||
if (col == imcol(viewer.col_page_start)) return tviewer.col_page_start;
|
||||
if (col == imcol(viewer.col_checksum)) return tviewer.col_checksum;
|
||||
if (col == imcol(viewer.col_highlight)) return tviewer.col_highlight;
|
||||
for (u64 section = 1; section < Sec_COUNT; ++section)
|
||||
if (col == imcol(viewer.col_section[section]))
|
||||
return tviewer.col_section[section];
|
||||
return ACol_None;
|
||||
}
|
||||
|
||||
internal
|
||||
String8 render_legend_to_string(Arena *arena, const Term_Viewer &viewer, const App_State &app)
|
||||
{
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
defer { scratch_end(scratch); };
|
||||
|
||||
struct String8_Node {
|
||||
String8_Node *next;
|
||||
String8 str;
|
||||
} *head = nullptr, *tail = nullptr;
|
||||
|
||||
String8 color_none = ansi_color_table[ACol_None];
|
||||
u64 tot_len = 0;
|
||||
for (u64 section = 1; section < Sec_COUNT; ++section) {
|
||||
Ansi_Color color = viewer.col_section[section];
|
||||
String8 color_str = ansi_color_table[color];
|
||||
String8 sec_name = section_names[section];
|
||||
Byte_Range range = get_section_range(app, static_cast<Section_Id>(section));
|
||||
String8 s;
|
||||
if (range.len)
|
||||
s = push_str8f(scratch.arena, "%s%20s %s0x%lX - 0x%lX [%lu B]\n",
|
||||
color_str.c(), sec_name.c(), color_none.c(),
|
||||
range.start, range.end(), range.len);
|
||||
else if (section == Sec_Page)
|
||||
s = push_str8f(scratch.arena, "%s%20s %s(%lu) [%lu B] \n",
|
||||
color_str.c(), sec_name.c(), color_none.c(), app.rndata.n_pages, app.rndata.tot_page_size);
|
||||
else if (section == Sec_Page_List)
|
||||
s = push_str8f(scratch.arena, "%s%20s %s(%lu) [%lu B]\n",
|
||||
color_str.c(), sec_name.c(), color_none.c(), app.rndata.n_cluster_groups, app.rndata.tot_page_list_size);
|
||||
String8_Node *node = arena_push<String8_Node>(scratch.arena);
|
||||
node->str = s;
|
||||
if (!tail) {
|
||||
head = node;
|
||||
} else {
|
||||
tail->next = node;
|
||||
}
|
||||
tail = node;
|
||||
tot_len += s.size;
|
||||
}
|
||||
|
||||
String8 legend { arena_push_array_nozero<u8>(arena, tot_len + 1), tot_len + 1 };
|
||||
legend.str[tot_len] = 0;
|
||||
u64 cur_size = 0;
|
||||
|
||||
for (String8_Node *node = head; node; node = node->next) {
|
||||
memcpy(legend.str + cur_size, node->str.str, node->str.size);
|
||||
cur_size += node->str.size;
|
||||
}
|
||||
|
||||
return legend;
|
||||
}
|
||||
|
||||
internal
|
||||
String8 render_range_to_string(Arena *arena, App_State &app, u64 len, u64 n_cols)
|
||||
{
|
||||
Term_Viewer viewer = make_term_viewer();
|
||||
|
||||
String8 legend = render_legend_to_string(arena, viewer, app);
|
||||
|
||||
// scratch must be created after `render_legend_to_string`
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
defer { scratch_end(scratch); };
|
||||
|
||||
String8 ntpl_desc = rntuple_description(scratch.arena, app.rndata);
|
||||
String8 header_str = push_str8f(scratch.arena, "RNTuple '%s' (%s) from file \"%s\"",
|
||||
app.ntpl_name.c(), ntpl_desc.c(), app.inspected_file.name.c());
|
||||
|
||||
if (n_cols == 0)
|
||||
n_cols = 32;
|
||||
u64 n_lines = len / n_cols;
|
||||
u64 max_addr = app.inspected_file.size;
|
||||
|
||||
// Calculate how much space do we need to fix line addr numbers.
|
||||
// The biggest address is gonna take up the most space
|
||||
const f32 RECP_LOG_16 = 0.360673760222f;
|
||||
// NOTE: +1 for the ":"
|
||||
u64 max_addr_len = 1 + ceilf(logf(max_addr + 1) * RECP_LOG_16);
|
||||
const u64 n_spaces_after_line_addr = 3;
|
||||
u64 line_addr_size = (max_addr_len + n_spaces_after_line_addr) * n_lines;
|
||||
String8 line_addr_fmt = push_str8f(scratch.arena, "%%0%dlX:", max_addr_len - 1);
|
||||
|
||||
// NOTE: +3 because we need 2 chars for each byte + 1 whitespace.
|
||||
u64 buf_size = (ACOL_MAX_LEN + 3) * len;
|
||||
buf_size += n_lines; // one newline per line
|
||||
buf_size += n_lines * ACOL_MAX_LEN; // reset color every line
|
||||
buf_size += line_addr_size;
|
||||
buf_size += 1; // initial newline
|
||||
buf_size += header_str.size;
|
||||
buf_size += 2; // two newlines after header
|
||||
buf_size += 2; // two newlines before legend
|
||||
buf_size += legend.size;
|
||||
buf_size += 1; // trailing zero
|
||||
u64 arena_start_pos = arena_pos(arena);
|
||||
char *buf = arena_push_array_nozero<char>(arena, buf_size);
|
||||
buf[len] = 0;
|
||||
|
||||
u64 start = app.viewer.base_display_addr;
|
||||
|
||||
// We need to properly initialize this before calling mem_edit_bg_color_fn!
|
||||
app.last_pinfo = &invalid_pinfo;
|
||||
|
||||
const u8 *data = app.inspected_file.mem;
|
||||
assert(start <= max_addr);
|
||||
len = min(len, max_addr - start);
|
||||
|
||||
char *cur = buf;
|
||||
*cur++ = '\n';
|
||||
|
||||
// header
|
||||
memcpy(cur, header_str.str, header_str.size);
|
||||
cur += header_str.size;
|
||||
*cur++ = '\n';
|
||||
*cur++ = '\n';
|
||||
|
||||
// first line addr
|
||||
cur += sprintf(cur, line_addr_fmt.c(), start);
|
||||
for (u32 i = 0; i < n_spaces_after_line_addr; ++i)
|
||||
*cur++ = ' ';
|
||||
|
||||
String8 col_none = ansi_color_table[ACol_None];
|
||||
|
||||
for (u64 i = 0; i < len; ++i) {
|
||||
u64 off = start + i;
|
||||
|
||||
/// Select color
|
||||
u32 byte_col = mem_edit_bg_color_fn(data + start, i, &app);
|
||||
// piggyback off mem_edit_bg_color_fn instead of rewriting the color-choosing logic.
|
||||
// Kinda dumb code but we don't need speed here since we're printing this one-off.
|
||||
Ansi_Color acol = viewer_to_ansi_color(app.viewer, viewer, byte_col);
|
||||
String8 acol_str = ansi_color_table[acol];
|
||||
memcpy(cur, acol_str.str, acol_str.size);
|
||||
cur += acol_str.size;
|
||||
|
||||
/// Write the human-readable byte
|
||||
cur += sprintf(cur, "%02X ", data[off]);
|
||||
|
||||
// new line
|
||||
if ((i + 1) % n_cols == 0) {
|
||||
*cur++ = '\n';
|
||||
memcpy(cur, col_none.c(), col_none.size);
|
||||
cur += col_none.size;
|
||||
cur += sprintf(cur, line_addr_fmt.c(), off + 1);
|
||||
for (u32 i = 0; i < n_spaces_after_line_addr; ++i)
|
||||
*cur++ = ' ';
|
||||
}
|
||||
|
||||
assert((u64)(cur - buf) < (u64)buf_size);
|
||||
}
|
||||
|
||||
*cur++ = '\n';
|
||||
*cur++ = '\n';
|
||||
memcpy(cur, legend.str, legend.size);
|
||||
cur += legend.size;
|
||||
|
||||
*cur = 0;
|
||||
|
||||
u64 len_used = (u64)(cur - buf);
|
||||
arena_pop_to(arena, arena_start_pos + len_used);
|
||||
|
||||
return { (u8 *)buf, len_used };
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
struct Viewer {
|
||||
#ifndef RNT_NO_GFX
|
||||
MemoryEditor mem_edit;
|
||||
#endif
|
||||
|
||||
// Imgui colors
|
||||
f32 col_section[Sec_COUNT][3];
|
||||
|
|
191
src/render_term.cpp
Normal file
191
src/render_term.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
internal
|
||||
Term_Viewer make_term_viewer()
|
||||
{
|
||||
Term_Viewer viewer {};
|
||||
viewer.col_key = ACol_Green;
|
||||
viewer.col_page_start = ACol_Bright_Magenta;
|
||||
viewer.col_checksum = ACol_Yellow;
|
||||
viewer.col_highlight = ACol_Bright_White;
|
||||
viewer.col_section[Sec_RNTuple_Anchor] = ACol_Bright_Yellow;
|
||||
viewer.col_section[Sec_RNTuple_Header] = ACol_Cyan;
|
||||
viewer.col_section[Sec_RNTuple_Footer] = ACol_Blue;
|
||||
viewer.col_section[Sec_TFile_Header] = ACol_White;
|
||||
viewer.col_section[Sec_TFile_Object] = ACol_Grey;
|
||||
viewer.col_section[Sec_TFile_Info] = ACol_Red;
|
||||
viewer.col_section[Sec_TFile_FreeList] = ACol_Bright_Yellow;
|
||||
viewer.col_section[Sec_Page] = ACol_Magenta;
|
||||
viewer.col_section[Sec_Page_List] = ACol_Bright_Cyan;
|
||||
viewer.col_section[Sec_TKey_List] = ACol_Bright_Green;
|
||||
return viewer;
|
||||
}
|
||||
|
||||
internal
|
||||
Ansi_Color ansi_color_for_offset(u64 off, App_State &app, const Term_Viewer &viewer)
|
||||
{
|
||||
off += app.base_display_addr;
|
||||
|
||||
Section section = find_section(app, off);
|
||||
|
||||
if (section.highlighted) return viewer.col_highlight;
|
||||
if (section.id == Sec_Page && off == section.range.start) return viewer.col_page_start;
|
||||
if (off < section.range.start) return viewer.col_key;
|
||||
if (section.range.end() - section.post_size <= off && off < section.range.end()) return viewer.col_checksum;
|
||||
return viewer.col_section[section.id];
|
||||
}
|
||||
|
||||
internal
|
||||
String8 render_legend_to_string(Arena *arena, const Term_Viewer &viewer, const App_State &app)
|
||||
{
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
defer { scratch_end(scratch); };
|
||||
|
||||
struct String8_Node {
|
||||
String8_Node *next;
|
||||
String8 str;
|
||||
} *head = nullptr, *tail = nullptr;
|
||||
|
||||
String8 color_none = ansi_color_table[ACol_None];
|
||||
u64 tot_len = 0;
|
||||
for (u64 section = 1; section < Sec_COUNT; ++section) {
|
||||
Ansi_Color color = viewer.col_section[section];
|
||||
String8 color_str = ansi_color_table[color];
|
||||
String8 sec_name = section_names[section];
|
||||
Byte_Range range = get_section_range(app, static_cast<Section_Id>(section));
|
||||
String8 s;
|
||||
if (range.len)
|
||||
s = push_str8f(scratch.arena, "%s%20s %s0x%lX - 0x%lX [%lu B]\n",
|
||||
color_str.c(), sec_name.c(), color_none.c(),
|
||||
range.start, range.end(), range.len);
|
||||
else if (section == Sec_Page)
|
||||
s = push_str8f(scratch.arena, "%s%20s %s(%lu) [%lu B] \n",
|
||||
color_str.c(), sec_name.c(), color_none.c(), app.rndata.n_pages, app.rndata.tot_page_size);
|
||||
else if (section == Sec_Page_List)
|
||||
s = push_str8f(scratch.arena, "%s%20s %s(%lu) [%lu B]\n",
|
||||
color_str.c(), sec_name.c(), color_none.c(), app.rndata.n_cluster_groups, app.rndata.tot_page_list_size);
|
||||
String8_Node *node = arena_push<String8_Node>(scratch.arena);
|
||||
node->str = s;
|
||||
if (!tail) {
|
||||
head = node;
|
||||
} else {
|
||||
tail->next = node;
|
||||
}
|
||||
tail = node;
|
||||
tot_len += s.size;
|
||||
}
|
||||
|
||||
String8 legend { arena_push_array_nozero<u8>(arena, tot_len + 1), tot_len + 1 };
|
||||
legend.str[tot_len] = 0;
|
||||
u64 cur_size = 0;
|
||||
|
||||
for (String8_Node *node = head; node; node = node->next) {
|
||||
memcpy(legend.str + cur_size, node->str.str, node->str.size);
|
||||
cur_size += node->str.size;
|
||||
}
|
||||
|
||||
return legend;
|
||||
}
|
||||
|
||||
internal
|
||||
String8 render_range_to_string(Arena *arena, App_State &app, u64 len, u64 n_cols)
|
||||
{
|
||||
Term_Viewer viewer = make_term_viewer();
|
||||
|
||||
String8 legend = render_legend_to_string(arena, viewer, app);
|
||||
|
||||
// scratch must be created after `render_legend_to_string`
|
||||
Temp scratch = scratch_begin(&arena, 1);
|
||||
defer { scratch_end(scratch); };
|
||||
|
||||
String8 ntpl_desc = rntuple_description(scratch.arena, app.rndata);
|
||||
String8 header_str = push_str8f(scratch.arena, "RNTuple '%s' (%s) from file \"%s\"",
|
||||
app.ntpl_name.c(), ntpl_desc.c(), app.inspected_file.name.c());
|
||||
|
||||
if (n_cols == 0)
|
||||
n_cols = 32;
|
||||
u64 n_lines = len / n_cols;
|
||||
u64 max_addr = app.inspected_file.size;
|
||||
|
||||
// Calculate how much space do we need to fix line addr numbers.
|
||||
// The biggest address is gonna take up the most space
|
||||
const f32 RECP_LOG_16 = 0.360673760222f;
|
||||
// NOTE: +1 for the ":"
|
||||
u64 max_addr_len = 1 + ceilf(logf(max_addr + 1) * RECP_LOG_16);
|
||||
const u64 n_spaces_after_line_addr = 3;
|
||||
u64 line_addr_size = (max_addr_len + n_spaces_after_line_addr) * n_lines;
|
||||
String8 line_addr_fmt = push_str8f(scratch.arena, "%%0%dlX:", max_addr_len - 1);
|
||||
|
||||
// NOTE: +3 because we need 2 chars for each byte + 1 whitespace.
|
||||
u64 buf_size = (ACOL_MAX_LEN + 3) * len;
|
||||
buf_size += n_lines; // one newline per line
|
||||
buf_size += n_lines * ACOL_MAX_LEN; // reset color every line
|
||||
buf_size += line_addr_size;
|
||||
buf_size += 1; // initial newline
|
||||
buf_size += header_str.size;
|
||||
buf_size += 2; // two newlines after header
|
||||
buf_size += 2; // two newlines before legend
|
||||
buf_size += legend.size;
|
||||
buf_size += 1; // trailing zero
|
||||
u64 arena_start_pos = arena_pos(arena);
|
||||
char *buf = arena_push_array_nozero<char>(arena, buf_size);
|
||||
buf[len] = 0;
|
||||
|
||||
u64 start = app.base_display_addr;
|
||||
|
||||
// We need to properly initialize this before calling mem_edit_bg_color_fn!
|
||||
app.last_pinfo = &invalid_pinfo;
|
||||
|
||||
const u8 *data = app.inspected_file.mem;
|
||||
assert(start <= max_addr);
|
||||
len = min(len, max_addr - start);
|
||||
|
||||
char *cur = buf;
|
||||
*cur++ = '\n';
|
||||
|
||||
// header
|
||||
memcpy(cur, header_str.str, header_str.size);
|
||||
cur += header_str.size;
|
||||
*cur++ = '\n';
|
||||
*cur++ = '\n';
|
||||
|
||||
// first line addr
|
||||
cur += sprintf(cur, line_addr_fmt.c(), start);
|
||||
for (u32 i = 0; i < n_spaces_after_line_addr; ++i)
|
||||
*cur++ = ' ';
|
||||
|
||||
String8 col_none = ansi_color_table[ACol_None];
|
||||
|
||||
for (u64 i = 0; i < len; ++i) {
|
||||
u64 off = start + i;
|
||||
|
||||
/// Select color
|
||||
Ansi_Color acol = ansi_color_for_offset(i, app, viewer);
|
||||
String8 acol_str = ansi_color_table[acol];
|
||||
memcpy(cur, acol_str.str, acol_str.size);
|
||||
cur += acol_str.size;
|
||||
|
||||
/// Write the human-readable byte
|
||||
cur += sprintf(cur, "%02X ", data[off]);
|
||||
|
||||
// new line
|
||||
if ((i + 1) % n_cols == 0) {
|
||||
*cur++ = '\n';
|
||||
memcpy(cur, col_none.c(), col_none.size);
|
||||
cur += col_none.size;
|
||||
cur += sprintf(cur, line_addr_fmt.c(), off + 1);
|
||||
for (u32 i = 0; i < n_spaces_after_line_addr; ++i)
|
||||
*cur++ = ' ';
|
||||
}
|
||||
|
||||
assert((u64)(cur - buf) < (u64)buf_size);
|
||||
}
|
||||
|
||||
*cur++ = '\n';
|
||||
*cur++ = '\n';
|
||||
memcpy(cur, legend.str, legend.size);
|
||||
cur += legend.size;
|
||||
|
||||
*cur++ = 0;
|
||||
u64 len_used = (u64)(cur - buf);
|
||||
|
||||
return { (u8 *)buf, len_used };
|
||||
}
|
|
@ -311,3 +311,98 @@ RNTuple_Data get_rntuple_data(Arena *arena, const Inspected_File &file, String8
|
|||
return rndata;
|
||||
}
|
||||
|
||||
internal
|
||||
Byte_Range get_section_range(const App_State &app, Section_Id sec)
|
||||
{
|
||||
switch (sec) {
|
||||
default: return { 0, 0 };
|
||||
case Sec_TFile_Header: return Byte_Range { 0, app.tfile_data.root_file_header_size };
|
||||
case Sec_TFile_Object: return app.tfile_data.rng_root_file_obj;
|
||||
case Sec_TFile_Info: return app.tfile_data.rng_root_file_info;
|
||||
case Sec_TFile_FreeList: return app.tfile_data.rng_root_file_free;
|
||||
case Sec_TKey_List: return app.rndata.rng_tkeys_list;
|
||||
case Sec_RNTuple_Anchor: return app.rndata.rng_anchor;
|
||||
case Sec_RNTuple_Header: return app.rndata.rng_header;
|
||||
case Sec_RNTuple_Footer: return app.rndata.rng_footer;
|
||||
}
|
||||
}
|
||||
|
||||
internal
|
||||
Section find_section(App_State &app, u64 off, i64 hilite_cluster = -1)
|
||||
{
|
||||
const RNTuple_Data &rdata = app.rndata;
|
||||
const TFile_Data &tdata = app.tfile_data;
|
||||
u64 rblob_sz = rdata.rblob_header_size; // @Incomplete
|
||||
b8 hilite = false;
|
||||
|
||||
// TFile start
|
||||
if (off <= tdata.root_file_header_size) return { Sec_TFile_Header, { 0, tdata.root_file_header_size }, 0, hilite };
|
||||
if (tdata.rng_root_file_obj.start <= off && off < tdata.rng_root_file_obj.end()) return { Sec_TFile_Object, tdata.rng_root_file_obj, 0, hilite };
|
||||
if (tdata.rng_root_file_info.start <= off && off < tdata.rng_root_file_info.end()) return { Sec_TFile_Info, tdata.rng_root_file_info, 0, hilite };
|
||||
if (tdata.rng_root_file_free.start <= off && off < tdata.rng_root_file_free.end()) return { Sec_TFile_FreeList, tdata.rng_root_file_free, 0, hilite };
|
||||
|
||||
/// Handle pages
|
||||
{
|
||||
// fast case: `off` is in the same page info as previous `off`.
|
||||
if (app.last_pinfo->range.start < off && off < app.last_pinfo->range.end()) {
|
||||
hilite = hilite_cluster >= 0 && app.last_pinfo->cluster_id == (u64)hilite_cluster;
|
||||
return { Sec_Page, app.last_pinfo->range, app.last_pinfo->checksum_size(), hilite };
|
||||
}
|
||||
|
||||
// still fast case: `off is in the next page info as the previous.
|
||||
if (app.last_pinfo->next) // don't check if it's checksum, since it's the first byte of the page
|
||||
app.last_pinfo = app.last_pinfo->next;
|
||||
if (app.last_pinfo && app.last_pinfo->range.start <= off && off < app.last_pinfo->range.end()) {
|
||||
hilite = hilite_cluster >= 0 && app.last_pinfo->cluster_id == (u64)hilite_cluster;
|
||||
return { Sec_Page, app.last_pinfo->range, app.last_pinfo->checksum_size(), hilite };
|
||||
}
|
||||
}
|
||||
|
||||
if (rdata.rng_anchor_key.start <= off && off < rdata.rng_anchor.end())
|
||||
return { Sec_RNTuple_Anchor, rdata.rng_anchor, 8, hilite };
|
||||
|
||||
if (rdata.rng_header.start - rblob_sz <= off && off < rdata.rng_header.end())
|
||||
return { Sec_RNTuple_Header, rdata.rng_header, 8, hilite };
|
||||
|
||||
if (rdata.rng_footer.start - rblob_sz <= off && off < rdata.rng_footer.end())
|
||||
return { Sec_RNTuple_Footer, rdata.rng_footer, 8, hilite };
|
||||
|
||||
if (rdata.rng_tkeys_list.start <= off && off < rdata.rng_tkeys_list.end())
|
||||
return { Sec_TKey_List, rdata.rng_tkeys_list, 0, hilite };
|
||||
|
||||
// @Speed
|
||||
for (u64 cg_idx = 0; cg_idx < rdata.n_cluster_groups; ++cg_idx) {
|
||||
Cluster_Group_Info &cg_info = rdata.cluster_groups[cg_idx];
|
||||
if (cg_info.rng_page_list.start - rblob_sz <= off && off < cg_info.rng_page_list.end())
|
||||
return { Sec_Page_List, cg_info.rng_page_list, 8, hilite };
|
||||
}
|
||||
|
||||
// Slow page group lookup, ideally only done once per render when last_pinfo is invalid.
|
||||
for (Page_Info_Chunk *chunk = rdata.page_chunks; chunk; chunk = chunk->next) {
|
||||
// If we're at the start of a chunk, return a fake Sec_Page used to highlight the RBlob header bytes.
|
||||
if (chunk->range.start - rblob_sz <= off && off < chunk->range.start)
|
||||
return { Sec_Page, { chunk->range.start, 0 }, 0, hilite };
|
||||
|
||||
if (chunk->range.start <= off && off < chunk->range.end()) {
|
||||
for (u64 group_idx = chunk->first_group; group_idx < rdata.n_page_groups; ++group_idx) {
|
||||
const Page_Info_Group &group = rdata.page_groups[group_idx];
|
||||
if (off < group.range.start || off >= group.range.end())
|
||||
continue;
|
||||
|
||||
for (Page_Info_Node *pinfo = group.first; pinfo; pinfo = pinfo->next) {
|
||||
if (pinfo->range.start <= off && off < pinfo->range.end()) {
|
||||
app.last_pinfo = pinfo;
|
||||
hilite = hilite_cluster >= 0 && pinfo->cluster_id == (u64)hilite_cluster;
|
||||
return { Sec_Page, pinfo->range, pinfo->checksum_size(), hilite };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Offset 0x%lX is in chunk 0x%lX - 0x%lX, but found in no page_info range!\n",
|
||||
off, chunk->range.start, chunk->range.end());
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
|
||||
#include <byteswap.h>
|
||||
#include <chrono>
|
||||
|
||||
|
@ -22,6 +24,7 @@
|
|||
#include <sanitizer/asan_interface.h>
|
||||
#endif
|
||||
|
||||
#ifndef RNT_NO_GFX
|
||||
#include <imgui/imgui.h>
|
||||
#include <imgui/backends/imgui_impl_glfw.h>
|
||||
#include <imgui/backends/imgui_impl_opengl3.h>
|
||||
|
@ -29,6 +32,7 @@
|
|||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <GLFW/glfw3.h>
|
||||
#endif
|
||||
|
||||
#define V_MAJOR "0"
|
||||
#define V_MINOR "1"
|
||||
|
@ -54,24 +58,13 @@ namespace chr = std::chrono;
|
|||
#include "mem.cpp"
|
||||
#include "str.cpp"
|
||||
#include "rntuple.cpp"
|
||||
#include "render.cpp"
|
||||
#include "mainloop.cpp"
|
||||
#include "render_term.cpp"
|
||||
#include "argparse.cpp"
|
||||
|
||||
internal
|
||||
b8 init_imgui(GLFWwindow* window) {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO(); (void) io;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 400");
|
||||
|
||||
return true;
|
||||
}
|
||||
#ifndef RNT_NO_GFX
|
||||
#include "render.cpp"
|
||||
#include "mainloop.cpp"
|
||||
#endif
|
||||
|
||||
internal
|
||||
void app_cleanup(App_State &app)
|
||||
|
@ -103,6 +96,13 @@ int main(int argc, char **argv)
|
|||
return 1;
|
||||
}
|
||||
|
||||
#ifdef RNT_NO_GFX
|
||||
if (!args.print_to_terminal) {
|
||||
fprintf(stderr, "rntviewer was compiled without graphics support. Please use -t to enable terminal mode.");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create app state
|
||||
App_State app {};
|
||||
defer { app_cleanup(app); };
|
||||
|
@ -121,8 +121,6 @@ int main(int argc, char **argv)
|
|||
app.ntpl_name = args.ntpl_name;
|
||||
app.tfile_data = get_tfile_data(app.inspected_file);
|
||||
app.rndata = get_rntuple_data(arena, app.inspected_file, app.ntpl_name);
|
||||
make_viewer(app, args.n_cols);
|
||||
app.viewer.base_display_addr = args.start_addr;
|
||||
|
||||
if (args.print_to_terminal) {
|
||||
u64 nbytes_displayed = args.nbytes_displayed;
|
||||
|
@ -133,6 +131,7 @@ int main(int argc, char **argv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifndef RNT_NO_GFX
|
||||
// Init imgui and GLFW
|
||||
GLFWwindow *window = init_glfw(800, 600);
|
||||
if (!window) {
|
||||
|
@ -146,8 +145,12 @@ int main(int argc, char **argv)
|
|||
return 1;
|
||||
}
|
||||
|
||||
make_viewer(app, args.n_cols);
|
||||
app.viewer.base_display_addr = args.start_addr;
|
||||
|
||||
// Start main loop
|
||||
run_main_loop(window, arena, app);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
10
src/str.cpp
10
src/str.cpp
|
@ -26,3 +26,13 @@ String8 str8_from_c(const char *str)
|
|||
u64 size = strlen(str);
|
||||
return String8 { (u8*)str, size };
|
||||
}
|
||||
|
||||
internal
|
||||
String8 to_pretty_size(Arena *arena, u64 bytes)
|
||||
{
|
||||
if (bytes >= GiB(1)) return push_str8f(arena, "%.1f GiB", (f32)bytes / GiB(1));
|
||||
if (bytes >= MiB(1)) return push_str8f(arena, "%.1f MiB", (f32)bytes / MiB(1));
|
||||
if (bytes >= KiB(1)) return push_str8f(arena, "%.1f KiB", (f32)bytes / KiB(1));
|
||||
return push_str8f(arena, "%zu B", bytes);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue