426 lines
15 KiB
C++
426 lines
15 KiB
C++
constexpr f32 max3(f32 a, f32 b, f32 c)
|
|
{
|
|
return (a > b) ? (a > c) ? a : (b > c) ? b : c : (b > c) ? b : c;
|
|
}
|
|
|
|
struct Color_Rgb { f32 r, g, b; };
|
|
|
|
// https://stackoverflow.com/questions/141855/programmatically-lighten-a-color
|
|
internal
|
|
Color_Rgb redist_rgb(Color_Rgb c)
|
|
{
|
|
f32 r = c.r;
|
|
f32 g = c.g;
|
|
f32 b = c.b;
|
|
f32 threshold = 255.999;
|
|
f32 m = max3(r, g, b);
|
|
if (m <= threshold)
|
|
return { r, g, b };
|
|
f32 total = r + g + b;
|
|
if (total >= 3 * threshold)
|
|
return { threshold, threshold, threshold };
|
|
f32 x = (3 * threshold - total) / (3 * m - total);
|
|
f32 gray = threshold - x * m;
|
|
return { gray + x * r, gray + x * g, gray + x * b };
|
|
}
|
|
|
|
internal
|
|
Color_Rgb brighten(Color_Rgb c, f32 amt)
|
|
{
|
|
f32 a = 1.f + amt;
|
|
Color_Rgb cb = { c.r * a, c.g * a, c.b * a };
|
|
cb = redist_rgb(cb);
|
|
return cb;
|
|
}
|
|
|
|
internal
|
|
void accum_dt_ms(Delta_Time_Accum &accum, f32 dt)
|
|
{
|
|
if (accum.count < accum.max) {
|
|
assert(accum.start == 0);
|
|
accum.base[accum.count++] = dt;
|
|
} else {
|
|
accum.base[accum.start++] = dt;
|
|
if (accum.start == accum.max)
|
|
accum.start = 0;
|
|
}
|
|
}
|
|
|
|
internal
|
|
f32 calc_avg_dt_ms(const Delta_Time_Accum &accum)
|
|
{
|
|
f32 res = 0;
|
|
for (u16 idx = 0; idx < accum.count; ++idx)
|
|
res += accum.base[idx];
|
|
if (accum.count) res /= accum.count;
|
|
return res;
|
|
}
|
|
|
|
internal
|
|
ImColor imcol(const f32 col[3], f32 brighten_amt = 0)
|
|
{
|
|
Color_Rgb c = { col[0], col[1], col[2] };
|
|
if (brighten_amt != 0)
|
|
c = brighten(c, brighten_amt);
|
|
|
|
return ImColor(c.r, c.g, c.b);
|
|
}
|
|
|
|
internal
|
|
u32 highlight_zstd_header(const u8 *data, u64 off, App_State &app, f32 brighten)
|
|
{
|
|
if (app.viewer.highlight_zstd_headers) {
|
|
static const u32 ZSTD_HEADER = 0xFD2FB528;
|
|
|
|
if (app.viewer.last_seen_zstd_header_start > 0) {
|
|
if (off - app.viewer.last_seen_zstd_header_start <= sizeof(ZSTD_HEADER)) {
|
|
return imcol(app.viewer.col_highlight, brighten);
|
|
}
|
|
app.viewer.last_seen_zstd_header_start = 0;
|
|
}
|
|
|
|
if (off < app.inspected_file.size - sizeof(ZSTD_HEADER) && data[off] == 0x28) {
|
|
u32 bytes;
|
|
memcpy(&bytes, data + off, sizeof(ZSTD_HEADER));
|
|
if (bytes == ZSTD_HEADER) {
|
|
app.viewer.last_seen_zstd_header_start = off;
|
|
return imcol(app.viewer.col_highlight, brighten);
|
|
}
|
|
}
|
|
} else {
|
|
app.viewer.last_seen_zstd_header_start = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
internal
|
|
u32 mem_edit_bg_color_fn(const u8 *data, u64 off, void *user_data)
|
|
{
|
|
App_State *app = reinterpret_cast<App_State *>(user_data);
|
|
off += app->base_display_addr;
|
|
|
|
f32 brighten = 0;
|
|
if (app->viewer.hovered_range.start <= off && off < app->viewer.hovered_range.end())
|
|
brighten = 0.3;
|
|
|
|
u32 col = highlight_zstd_header(data, off, *app, brighten);
|
|
if (app->viewer.last_seen_zstd_header_start)
|
|
return col;
|
|
|
|
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, brighten);
|
|
if (section.id == Sec_Page && off == section.range.start) return imcol(app->viewer.col_page_start, brighten);
|
|
if (off < section.range.start) return imcol(app->viewer.col_key, brighten);
|
|
if (section.range.end() - section.post_size <= off && off < section.range.end()) return imcol(app->viewer.col_checksum, brighten);
|
|
return imcol(app->viewer.col_section[section.id], brighten);
|
|
}
|
|
|
|
internal
|
|
MemoryEditor make_memory_editor(App_State &app, i32 n_cols)
|
|
{
|
|
MemoryEditor mem_edit;
|
|
mem_edit.Cols = n_cols ? n_cols : 32;
|
|
mem_edit.OptShowDataPreview = true;
|
|
// Do nothing on write.
|
|
// Note that we don't use ReadOnly = true because that disables selecting bytes
|
|
mem_edit.WriteFn = [] (ImU8*, size_t, ImU8, void*) {};
|
|
mem_edit.BgColorFn = mem_edit_bg_color_fn;
|
|
mem_edit.UserData = &app;
|
|
return mem_edit;
|
|
}
|
|
|
|
internal
|
|
void make_viewer(App_State &app, u16 n_cols)
|
|
{
|
|
Viewer viewer {};
|
|
viewer.mem_edit = make_memory_editor(app, (i32)n_cols);
|
|
|
|
#define COL(c, r, g, b) viewer.c[0] = r/255.0, viewer.c[1] = g/255.0, viewer.c[2] = b/255.0
|
|
COL(col_key, 0, 100, 50);
|
|
COL(col_page_start, 200, 0, 200);
|
|
COL(col_checksum, 134, 65, 25);
|
|
COL(col_highlight, 190, 190, 190);
|
|
#define COL_S(c, r, g, b) viewer.col_section[c][0] = r/255.0, viewer.col_section[c][1] = g/255.0, viewer.col_section[c][2] = b/255.0
|
|
COL_S(Sec_RNTuple_Anchor, 150, 150, 0);
|
|
COL_S(Sec_RNTuple_Header, 150, 0, 50);
|
|
COL_S(Sec_RNTuple_Footer, 50, 0, 150);
|
|
COL_S(Sec_TFile_Header, 90, 90, 90);
|
|
COL_S(Sec_TFile_Object, 120, 120, 120);
|
|
COL_S(Sec_TFile_Info, 95, 76, 76);
|
|
COL_S(Sec_TFile_FreeList, 60, 60, 90);
|
|
COL_S(Sec_Page, 125, 0, 125);
|
|
COL_S(Sec_Page_List, 60, 110, 120);
|
|
COL_S(Sec_TKey_List, 100, 140, 100);
|
|
#undef COL
|
|
#undef COL_S
|
|
|
|
app.viewer = viewer;
|
|
}
|
|
|
|
internal
|
|
void viewer_jump_to(App_State &app, u64 addr)
|
|
{
|
|
app.viewer.mem_edit.GotoAddr = addr;
|
|
}
|
|
|
|
internal
|
|
Page_Info_Node *viewer_jump_to_page(App_State &app, u64 page_idx)
|
|
{
|
|
if (app.rndata.n_pages == 0)
|
|
return nullptr;
|
|
page_idx = (page_idx + app.rndata.n_pages) % app.rndata.n_pages;
|
|
|
|
// @Speed
|
|
Page_Info_Node *page = app.rndata.pages;
|
|
for (u64 i = 0; i < page_idx; ++i) {
|
|
page = page->next;
|
|
assert(page);
|
|
}
|
|
|
|
app.viewer.latest_page_gone_to = page_idx;
|
|
viewer_jump_to(app, page->range.start);
|
|
|
|
return page;
|
|
}
|
|
|
|
internal
|
|
void viewer_jump_to_page_list(App_State &app, u64 page_list_idx)
|
|
{
|
|
if (app.rndata.n_cluster_groups == 0)
|
|
return;
|
|
page_list_idx = (page_list_idx + app.rndata.n_cluster_groups) % app.rndata.n_cluster_groups;
|
|
|
|
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, cg_info.rng_page_list.start);
|
|
}
|
|
|
|
internal
|
|
void viewer_jump_to_cluster(App_State &app, u64 cluster_idx)
|
|
{
|
|
if (app.rndata.n_clusters == 0)
|
|
return;
|
|
cluster_idx = (cluster_idx + app.rndata.n_clusters) % app.rndata.n_clusters;
|
|
|
|
Cluster_Info &cluster = app.rndata.clusters[cluster_idx];
|
|
Page_Info_Node *page = cluster.first_page;
|
|
assert(page);
|
|
|
|
app.viewer.highlighted_cluster = cluster_idx;
|
|
app.viewer.latest_page_gone_to = cluster.first_page_idx;
|
|
viewer_jump_to(app, page->range.start);
|
|
}
|
|
|
|
internal
|
|
void imgui_render_string_tree(Arena *arena, String8_Node *root, String8_Node *highlighted, u16 indent = 0)
|
|
{
|
|
String8 indent_str;
|
|
if (indent) {
|
|
indent_str = { arena_push_array_nozero<u8>(arena, indent + 1), indent };
|
|
indent_str.str[indent] = 0;
|
|
memset(indent_str.str, ' ', indent);
|
|
} else {
|
|
indent_str = str8("");
|
|
}
|
|
|
|
for (String8_Node *node = root; node; node = node->next) {
|
|
if (node == highlighted)
|
|
ImGui::TextColored(ImColor(1.0f, 1.0f, 0.0f), "%s%s", indent_str.c(), node->str.c());
|
|
else
|
|
ImGui::Text("%s%s", indent_str.c(), node->str.c());
|
|
|
|
if (node->first_child)
|
|
imgui_render_string_tree(arena, node->first_child, highlighted, indent + 2);
|
|
}
|
|
}
|
|
|
|
internal
|
|
void update_and_render(Arena *arena, App_State &app, f32 delta_time_ms)
|
|
{
|
|
accum_dt_ms(app.delta_time_accum, delta_time_ms);
|
|
|
|
ImGui::SetNextWindowPos({ 0, 0 });
|
|
ImGui::SetNextWindowSize({ (f32)app.win_data.width, (f32)app.win_data.height });
|
|
|
|
Temp scratch = scratch_begin(&arena, 1);
|
|
defer { scratch_end(scratch); };
|
|
|
|
const auto main_win_flags = ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration;
|
|
if (ImGui::Begin("main", nullptr, main_win_flags)) {
|
|
|
|
String8 ntpl_desc = rntuple_description(scratch.arena, app.rndata);
|
|
ImGui::Text("RNTuple '%s' (%s) from file \"%s\"", app.ntpl_name.c(), ntpl_desc.c(), app.inspected_file.name.c());
|
|
|
|
// Draw stats
|
|
{
|
|
ImGui::SameLine();
|
|
|
|
f32 avg_dt = calc_avg_dt_ms(app.delta_time_accum);
|
|
|
|
String8 mem_used = to_pretty_size(scratch.arena, arena->mem_used);
|
|
String8 mem_peak = to_pretty_size(scratch.arena, arena->mem_peak_used);
|
|
String8 stat_txt = push_str8f(scratch.arena, "mem used: %s (peak: %s) | avg dt: %.1f",
|
|
mem_used.c(), mem_peak.c(), avg_dt);
|
|
|
|
f32 pos_x = (ImGui::GetCursorPosX() + ImGui::GetColumnWidth() - ImGui::CalcTextSize(stat_txt.c()).x
|
|
- ImGui::GetScrollX() - 2 * ImGui::GetStyle().ItemSpacing.x);
|
|
if (pos_x > ImGui::GetCursorPosX())
|
|
ImGui::SetCursorPosX(pos_x);
|
|
|
|
ImGui::Text("%s", stat_txt.c());
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
// Draw main content
|
|
{
|
|
const i64 step_i64 = 1;
|
|
|
|
ImGui::BeginTable("Hex View", 2, ImGuiTableFlags_Resizable);
|
|
|
|
ImGui::TableNextColumn();
|
|
|
|
// Absolute byte offset into inspected_file.mem that has been hovered last.
|
|
// 0 means "invalid", otherwise the actual offset is `hovered_off - 1`.
|
|
u64 hovered_off = app.viewer.mem_edit.MouseHovered * (app.viewer.mem_edit.MouseHoveredAddr + 1);
|
|
|
|
assert(app.base_display_addr < app.inspected_file.size);
|
|
void *content = app.inspected_file.mem + app.base_display_addr;
|
|
u64 content_size = app.inspected_file.size - app.base_display_addr;
|
|
app.last_pinfo = &invalid_pinfo;
|
|
app.viewer.mem_edit.DrawContents(content, content_size, app.base_display_addr);
|
|
|
|
ImGui::TableNextColumn();
|
|
const ImGuiColorEditFlags edit_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoLabel;
|
|
const ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_EnterReturnsTrue;
|
|
|
|
// Unique sections: just display a button that jumps to the start of it and show their size
|
|
for (u32 i = 0; i < Sec_COUNT; ++i) {
|
|
Byte_Range range = get_section_range(app, static_cast<Section_Id>(i));
|
|
if (!range.len) continue;
|
|
|
|
String8 sec_name = section_names[i];
|
|
String8 col_label = push_str8f(scratch.arena, "_%s", sec_name.c());
|
|
ImGui::ColorEdit3(col_label.c(), app.viewer.col_section[i], edit_flags);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(sec_name.c()))
|
|
viewer_jump_to(app, range.start);
|
|
ImGui::SameLine();
|
|
ImGui::Text("%s", to_pretty_size(scratch.arena, range.len).c());
|
|
}
|
|
|
|
// Repeated sections: allow jumping to the N-th
|
|
ImGui::ColorEdit3("_TKey Header", app.viewer.col_key, edit_flags);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("TKey Header")) {} // TODO: jump to next key
|
|
|
|
ImGui::ColorEdit3("_Page Start", app.viewer.col_page_start, edit_flags);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Page Start"))
|
|
app.viewer.latest_page = viewer_jump_to_page(app, 0);
|
|
|
|
ImGui::ColorEdit3("_Page", app.viewer.col_section[Sec_Page], edit_flags);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Page"))
|
|
app.viewer.latest_page = viewer_jump_to_page(app, app.viewer.latest_page_gone_to);
|
|
ImGui::SameLine();
|
|
{
|
|
const i64 step_fast_i64 = app.rndata.n_pages / 100;
|
|
i64 page_to_go_to = app.viewer.latest_page_gone_to;
|
|
ImGui::PushItemWidth(100.f);
|
|
if (ImGui::InputScalar("##page_viewed", ImGuiDataType_S64, &page_to_go_to, &step_i64, &step_fast_i64, "%u", input_flags))
|
|
app.viewer.latest_page = viewer_jump_to_page(app, page_to_go_to);
|
|
ImGui::PopItemWidth();
|
|
}
|
|
ImGui::SameLine();
|
|
String8 this_page_width = app.viewer.latest_page ? push_str8f(scratch.arena, " (this page: %s)", to_pretty_size(scratch.arena, app.viewer.latest_page->range.len).c()) : str8("");
|
|
ImGui::Text("%s%s", to_pretty_size(scratch.arena, app.rndata.tot_page_comp_size).c(), this_page_width.c());
|
|
|
|
ImGui::ColorEdit3("_Checksum", app.viewer.col_checksum, edit_flags);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Checksum")) {} // TODO jump to next checksum
|
|
|
|
ImGui::ColorEdit3("_Page List", app.viewer.col_section[Sec_Page_List], edit_flags);
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Page List"))
|
|
viewer_jump_to_page_list(app, app.viewer.latest_page_list_gone_to);
|
|
|
|
ImGui::SameLine();
|
|
{
|
|
i64 page_list_to_go_to = app.viewer.latest_page_list_gone_to;
|
|
ImGui::PushItemWidth(80.f);
|
|
if (ImGui::InputScalar("##page_list_viewed", ImGuiDataType_S64, &page_list_to_go_to, &step_i64, nullptr, "%u", input_flags))
|
|
viewer_jump_to_page_list(app, page_list_to_go_to);
|
|
ImGui::PopItemWidth();
|
|
}
|
|
ImGui::SameLine();
|
|
ImGui::Text("%s", to_pretty_size(scratch.arena, app.rndata.tot_page_list_size).c());
|
|
|
|
// -------------------------------
|
|
ImGui::Separator();
|
|
String8 root_version_str = push_str8f(scratch.arena, "%u.%u.%u",
|
|
app.tfile_data.root_version_major,
|
|
app.tfile_data.root_version_minor,
|
|
app.tfile_data.root_version_patch);
|
|
ImGui::Text("ROOT version: %s", root_version_str.c());
|
|
ImGui::Text("TFile compression: %u", app.tfile_data.compression);
|
|
ImGui::Text("Num pages: %lu", app.rndata.n_pages);
|
|
ImGui::Text("Num elements: %lu", app.rndata.n_elems);
|
|
ImGui::Text("Num entries: %lu", app.rndata.n_entries);
|
|
if (app.rndata.tot_page_uncomp_size) {
|
|
ImGui::Text("Pages uncompressed size: %s (comp.ratio = %.3f)",
|
|
to_pretty_size(scratch.arena, app.rndata.tot_page_uncomp_size).c(),
|
|
(f32)app.rndata.tot_page_comp_size / app.rndata.tot_page_uncomp_size);
|
|
}
|
|
|
|
{
|
|
const i64 step_fast_i64 = app.rndata.n_clusters / 100;
|
|
i64 cluster_to_highlight = app.viewer.highlighted_cluster;
|
|
ImGui::PushItemWidth(100.f);
|
|
if (ImGui::InputScalar("##highlighted_cluster", ImGuiDataType_S64, &cluster_to_highlight, &step_i64, &step_fast_i64, "%u", input_flags)) {
|
|
viewer_jump_to_cluster(app, cluster_to_highlight);
|
|
}
|
|
ImGui::PopItemWidth();
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("Highlight cluster", &app.viewer.highlight_cluster);
|
|
}
|
|
ImGui::Checkbox("Highlight ZSTD headers", &app.viewer.highlight_zstd_headers);
|
|
|
|
// ---------------------------------
|
|
ImGui::Separator();
|
|
|
|
if (hovered_off)
|
|
{
|
|
ImGui::TextColored(ImColor(0.f, 0.65f, 0.f), "Offset: 0x%" PRIX64, hovered_off - 1);
|
|
Section hovered_section = find_section(app, hovered_off - 1);
|
|
b8 hover_display_grouped = !(app.user_input.key_state[KEY_ALT] & KEY_STATE_IS_DOWN);
|
|
Sec_Hover_Info hover_info = get_section_hover_info(scratch.arena, hovered_section, hovered_off - 1,
|
|
app.inspected_file.mem, hover_display_grouped);
|
|
if (!app.rndata.header_is_compressed)
|
|
ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "(Hint: press Alt for single-field hover information)");
|
|
if (hovered_section.id == Sec_Page)
|
|
ImGui::TextColored(ImColor(0.5f, 0.5f, 0.5f), "(Hint: Shift-Click to jump to the start of this page)");
|
|
imgui_render_string_tree(scratch.arena, hover_info.desc->head, hover_info.highlighted_desc);
|
|
app.viewer.hovered_range = hover_info.rng;
|
|
|
|
// Shift-clicking on a page section will update the current page in the legend
|
|
if (hovered_section.id == Sec_Page && (app.user_input.key_state[KEY_SHIFT] & KEY_STATE_IS_DOWN) &&
|
|
(app.user_input.mouse_btn_state[MOUSE_BTN_LEFT] & MOUSE_BTN_STATE_JUST_PRESSED))
|
|
{
|
|
u64 page_id = ((const Page_Info_Node *)hovered_section.info)->page_id;
|
|
app.viewer.latest_page = viewer_jump_to_page(app, page_id);
|
|
}
|
|
} else {
|
|
app.viewer.hovered_range = {};
|
|
}
|
|
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
}
|