add no gfx compile flag

This commit is contained in:
silverweed 2024-07-25 18:23:58 +02:00
parent 4918c152b5
commit 1f3ddce98f
10 changed files with 356 additions and 349 deletions

View file

@ -53,3 +53,11 @@ asan: $(ROOT_IFACE_DBG) build/imgui.o
ubsan: $(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) $(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)

View file

@ -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

View file

@ -23,10 +23,14 @@ struct App_State {
User_Input user_input; User_Input user_input;
RNTuple_Data rndata; RNTuple_Data rndata;
TFile_Data tfile_data; TFile_Data tfile_data;
Viewer viewer;
Inspected_File inspected_file; Inspected_File inspected_file;
#ifndef RNT_NO_GFX
Viewer viewer;
#endif
String8 ntpl_name; String8 ntpl_name;
u64 base_display_addr;
Delta_Time_Accum delta_time_accum; Delta_Time_Accum delta_time_accum;

View file

@ -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 internal
void glfw_error_callback(i32 error, const char *description) { void glfw_error_callback(i32 error, const char *description) {
fprintf(stderr, "GLFW error %d: %s\n", error, description); fprintf(stderr, "GLFW error %d: %s\n", error, description);

View file

@ -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 internal
void accum_dt_ms(Delta_Time_Accum &accum, f32 dt) 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; 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 internal
ImColor imcol(const f32 col[3]) 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); App_State *app = reinterpret_cast<App_State *>(user_data);
off += app->viewer.base_display_addr; 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.highlighted) return imcol(app->viewer.col_highlight);
if (section.id == Sec_Page && off == section.range.start) return imcol(app->viewer.col_page_start); 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 internal
void viewer_jump_to(Viewer &viewer, u64 addr) void viewer_jump_to(App_State &app, u64 addr)
{ {
viewer.base_display_addr = addr; app.base_display_addr = addr;
viewer.mem_edit.GotoAddr = 0; app.viewer.mem_edit.GotoAddr = 0;
} }
internal internal
@ -218,7 +113,7 @@ void viewer_jump_to_page(App_State &app, u64 page_idx)
} }
app.viewer.latest_page_gone_to = 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 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]; Cluster_Group_Info &cg_info = app.rndata.cluster_groups[page_list_idx];
app.viewer.latest_page_list_gone_to = 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 internal
@ -250,7 +145,7 @@ void viewer_jump_to_cluster(App_State &app, u64 cluster_idx)
} }
app.viewer.highlighted_cluster = cluster_idx; app.viewer.highlighted_cluster = cluster_idx;
viewer_jump_to(app.viewer, page->range.start); viewer_jump_to(app, page->range.start);
} }
internal 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::ColorEdit3(col_label.c(), app.viewer.col_section[i], flags);
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button(sec_name.c())) if (ImGui::Button(sec_name.c()))
viewer_jump_to(app.viewer, range.start); viewer_jump_to(app, range.start);
ImGui::SameLine(); ImGui::SameLine();
ImGui::Text("%s", to_pretty_size(scratch.arena, range.len).c()); 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(); 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 };
}

View file

@ -1,5 +1,7 @@
struct Viewer { struct Viewer {
#ifndef RNT_NO_GFX
MemoryEditor mem_edit; MemoryEditor mem_edit;
#endif
// Imgui colors // Imgui colors
f32 col_section[Sec_COUNT][3]; f32 col_section[Sec_COUNT][3];

191
src/render_term.cpp Normal file
View 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 };
}

View file

@ -311,3 +311,98 @@ RNTuple_Data get_rntuple_data(Arena *arena, const Inspected_File &file, String8
return rndata; 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 {};
}

View file

@ -15,6 +15,8 @@
#include <cstdint> #include <cstdint>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <cassert>
#include <byteswap.h> #include <byteswap.h>
#include <chrono> #include <chrono>
@ -22,6 +24,7 @@
#include <sanitizer/asan_interface.h> #include <sanitizer/asan_interface.h>
#endif #endif
#ifndef RNT_NO_GFX
#include <imgui/imgui.h> #include <imgui/imgui.h>
#include <imgui/backends/imgui_impl_glfw.h> #include <imgui/backends/imgui_impl_glfw.h>
#include <imgui/backends/imgui_impl_opengl3.h> #include <imgui/backends/imgui_impl_opengl3.h>
@ -29,6 +32,7 @@
#define GLFW_INCLUDE_NONE #define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#endif
#define V_MAJOR "0" #define V_MAJOR "0"
#define V_MINOR "1" #define V_MINOR "1"
@ -54,24 +58,13 @@ namespace chr = std::chrono;
#include "mem.cpp" #include "mem.cpp"
#include "str.cpp" #include "str.cpp"
#include "rntuple.cpp" #include "rntuple.cpp"
#include "render.cpp" #include "render_term.cpp"
#include "mainloop.cpp"
#include "argparse.cpp" #include "argparse.cpp"
internal #ifndef RNT_NO_GFX
b8 init_imgui(GLFWwindow* window) { #include "render.cpp"
IMGUI_CHECKVERSION(); #include "mainloop.cpp"
ImGui::CreateContext(); #endif
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 internal
void app_cleanup(App_State &app) void app_cleanup(App_State &app)
@ -103,6 +96,13 @@ int main(int argc, char **argv)
return 1; 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 // Create app state
App_State app {}; App_State app {};
defer { app_cleanup(app); }; defer { app_cleanup(app); };
@ -121,8 +121,6 @@ int main(int argc, char **argv)
app.ntpl_name = args.ntpl_name; app.ntpl_name = args.ntpl_name;
app.tfile_data = get_tfile_data(app.inspected_file); app.tfile_data = get_tfile_data(app.inspected_file);
app.rndata = get_rntuple_data(arena, app.inspected_file, app.ntpl_name); 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) { if (args.print_to_terminal) {
u64 nbytes_displayed = args.nbytes_displayed; u64 nbytes_displayed = args.nbytes_displayed;
@ -133,6 +131,7 @@ int main(int argc, char **argv)
return 0; return 0;
} }
#ifndef RNT_NO_GFX
// Init imgui and GLFW // Init imgui and GLFW
GLFWwindow *window = init_glfw(800, 600); GLFWwindow *window = init_glfw(800, 600);
if (!window) { if (!window) {
@ -146,8 +145,12 @@ int main(int argc, char **argv)
return 1; return 1;
} }
make_viewer(app, args.n_cols);
app.viewer.base_display_addr = args.start_addr;
// Start main loop // Start main loop
run_main_loop(window, arena, app); run_main_loop(window, arena, app);
#endif
return 0; return 0;
} }

View file

@ -26,3 +26,13 @@ String8 str8_from_c(const char *str)
u64 size = strlen(str); u64 size = strlen(str);
return String8 { (u8*)str, size }; 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);
}