From 1f3ddce98fc0476d438c22f82d72ccf439786b48 Mon Sep 17 00:00:00 2001 From: silverweed Date: Thu, 25 Jul 2024 18:23:58 +0200 Subject: [PATCH] add no gfx compile flag --- Makefile | 8 ++ imgui.ini | 20 --- src/app_state.h | 6 +- src/mainloop.cpp | 15 +++ src/render.cpp | 319 ++------------------------------------------ src/render.h | 2 + src/render_term.cpp | 191 ++++++++++++++++++++++++++ src/rntuple.cpp | 95 +++++++++++++ src/rntviewer.cpp | 39 +++--- src/str.cpp | 10 ++ 10 files changed, 356 insertions(+), 349 deletions(-) delete mode 100644 imgui.ini create mode 100644 src/render_term.cpp diff --git a/Makefile b/Makefile index d39ed03..8a9aa7e 100644 --- a/Makefile +++ b/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) diff --git a/imgui.ini b/imgui.ini deleted file mode 100644 index 30abc47..0000000 --- a/imgui.ini +++ /dev/null @@ -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 - diff --git a/src/app_state.h b/src/app_state.h index d1df5b8..086580d 100644 --- a/src/app_state.h +++ b/src/app_state.h @@ -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; diff --git a/src/mainloop.cpp b/src/mainloop.cpp index 88565b9..d1e422e 100644 --- a/src/mainloop.cpp +++ b/src/mainloop.cpp @@ -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); diff --git a/src/render.cpp b/src/render.cpp index 0b9c63b..6d2f22d 100644 --- a/src/render.cpp +++ b/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(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)); - 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(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(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(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 }; -} diff --git a/src/render.h b/src/render.h index 359c4cf..5e68269 100644 --- a/src/render.h +++ b/src/render.h @@ -1,5 +1,7 @@ struct Viewer { +#ifndef RNT_NO_GFX MemoryEditor mem_edit; +#endif // Imgui colors f32 col_section[Sec_COUNT][3]; diff --git a/src/render_term.cpp b/src/render_term.cpp new file mode 100644 index 0000000..90177ed --- /dev/null +++ b/src/render_term.cpp @@ -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)); + 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(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(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(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 }; +} diff --git a/src/rntuple.cpp b/src/rntuple.cpp index c2220ad..f7c5db3 100644 --- a/src/rntuple.cpp +++ b/src/rntuple.cpp @@ -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 {}; +} diff --git a/src/rntviewer.cpp b/src/rntviewer.cpp index b4bfc67..a273254 100644 --- a/src/rntviewer.cpp +++ b/src/rntviewer.cpp @@ -15,6 +15,8 @@ #include #include #include +#include + #include #include @@ -22,6 +24,7 @@ #include #endif +#ifndef RNT_NO_GFX #include #include #include @@ -29,6 +32,7 @@ #define GLFW_INCLUDE_NONE #include +#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; } diff --git a/src/str.cpp b/src/str.cpp index 589aeb3..954b02f 100644 --- a/src/str.cpp +++ b/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); +} +