mcap/src/base/debug.jai
Musa Mahmood be9ad707c4 [0.1.17] mcap.exe FILE opens a file, beside a running mcap in view mode; a Command Line window; thumbnails no longer stuck on "Cannot read"
mcap.exe FILE, with FILE first (as Explorer's Open with passes it), opens an .mcap, image, video or
.snagx (app/open_file.jai). With no mcap running, the app starts as usual: an .mcap in the library
opens in the Capture window, anything else in the Opened File window, which says where the library
has it already (by its hash in .import-index) or imports it into the library folder typed or chosen.
With mcap running, a second window opens in view mode: the file alone, with Import, and Open in mcap
/ Show in mcap, which hand it to the running copy (a new Open_File message) and close the view. The
library is not loaded or watched, no model loads, and it has no tray icon, no global hotkeys, and
writes no settings or layout.

Import takes a single file as its source too (and -import FILE DEST); an .mcap is copied in as it is,
while a folder's import still skips them. -open-import DEST imports the opened file as its Import
button does, then exits.

-h and --help open a Command Line window listing the options, also toggled from the Debug Panel; with
mcap running, that one comes forward with it (a Show_Help message).

Thumbnails: the "First preview shown" log line, put between creating a preview's texture and its
else, had every preview after the first marked failed. Once its texture was released (scrolled far
away, or the switch from WARP to the GPU at startup) it was never decoded again and showed "Cannot
read".
2026-10-14 08:00:02 +01:00

262 lines
12 KiB
Plaintext

app_debug_panel_ui :: () {
if !app.show_debug_panel then return;
using ImGui;
SetNextWindowSize(imgui_scale(Vector2.{500, 400}), .FirstUseEver);
push_imgui_window("Debug Panel", p_open=*app.show_debug_panel);
style := GetStyle();
io := GetIO();
Spacing();
SeparatorText("Machine");
Text("Machine name: %", app.machine_name);
SameLine();
if SmallButton("Copy##machine_name") {
clipboard_set_text(app.machine_name);
toast_show(.Success, "Copied %", app.machine_name);
}
#if USING_WINDOWS {
Text("Running as administrator: %", app.is_admin);
Text("Large pages: %", ifx app.large_pages_allowed then "available" else "unavailable");
}
Spacing();
frame_time := 1000.0 / io.Framerate;
SeparatorText("Performance Metrics");
Text("% fps | % ms", FF(io.Framerate, 1), FF(frame_time, 3));
Text("Time to first frame = % ms", FF(perf.time_to_first_frame_ms, 1));
Checkbox("Frame Times window", *app.show_frame_time_graph);
ButtonTooltip(to_string(toggle_frame_times_hotkey));
Checkbox("Command Line window", *app.show_help_panel);
ButtonTooltip("The command-line options; mcap --help opens it");
Spacing();
SeparatorText("Memory Usage");
Text("Working set: % (peak: %)", format_bytes(perf.memory_usage.working_set), format_bytes(perf.memory_usage.peak_working_set));
Text("Virtual memory: % (peak: %)", format_bytes(perf.memory_usage.virtual_memory), format_bytes(perf.memory_usage.peak_virtual_memory));
#if DEBUG { if Button("Clear Unused Pages") { os_clear_unused_pages(); } }
Spacing();
SeparatorText("Fonts");
ShowFontSelector("Font Selector");
if DragFloat("FontSizeBase", *style.FontSizeBase, 0.2, 8, 64, "%.0f", .AlwaysClamp) {
style._NextFrameFontSizeBase = style.FontSizeBase; // #hack as of ImGui v1.92.7
}
if IsItemDeactivatedAfterEdit() app_save_user_settings_file_and_toast("font size");
// This is how we adjust the font size in user-mode!
DragFloat("FontScaleMain", *style.FontScaleMain, DEFAULT_FONT_SCALE_INCREMENT, FONT_SCALE_MIN, FONT_SCALE_MAX, flags = .AlwaysClamp);
if IsItemDeactivatedAfterEdit() app_save_user_settings_file_and_toast("font size");
Text("FontScaleDpi: %", style.FontScaleDpi); // should adjust automatically.
Text("Graphics Backend: %", perf.backend);
if perf.adapter_name.count > 0 then Text("Adapter: % (%)", perf.adapter_name, ifx perf.renderer_is_software then "software" else "hardware");
#if USING_WINDOWS {
Text("Startup device (%): % ms", ifx perf.started_on_warp then "WARP" else "hardware", FF(perf.startup_device_ms, 1));
Text("Startup swap chain: % ms", FF(perf.startup_swap_chain_ms, 1));
if perf.started_on_warp {
if perf.hw_device_ready_ms > 0 {
Text("Hardware device: created in % ms", FF(perf.hw_device_create_ms, 1));
Text("Hardware device: ready % ms after launch", FF(perf.hw_device_ready_ms, 1));
Text("Renderer swap: % ms", FF(perf.hw_swap_ms, 1));
} else {
Text("Hardware device: pending");
}
}
}
Spacing();
SeparatorText("Theme");
current := app_theme_current();
Text("Current theme: %1%2", ifx current then current.name else "(none)", ifx app_theme.dirty then " *" else "");
if Button("Open Theme Editor") app.show_style_editor = true;
ButtonTooltip(to_string(toggle_style_editor_hotkey));
Spacing();
SeparatorText("Settings");
settings_path := app_user_settings_path();
if Button("Open settings.config") {
if !file_exists(settings_path) app_save_user_settings_file();
app_open_config_file(settings_path);
}
ButtonTooltip(settings_path);
TextColored(app_theme.palette.dim, "Applied when saved; the window size at the next start.");
if Button("Reset Layout") imgui_reset_layout_requested = true;
ButtonTooltip(tprint("Overwrites % with the default layout built into the app. (%)", to_string(io.IniFilename), to_string(reset_layout_hotkey)));
#if USING_SDL {
sdlv := SDL_GetVersion();
sdlr := SDL_GetRevision();
Text("SDL version: %, revision: %\n", sdlv, to_string(sdlr));
}
Spacing();
}
app_frame_times_ui :: () {
if !app.show_frame_time_graph return;
ImGui.SetNextWindowSize(imgui_scale(Vector2.{420, 160}), .FirstUseEver);
push_imgui_window("Frame Times", .NoScrollbar | .NoScrollWithMouse, p_open=*app.show_frame_time_graph);
frame_time_graph();
}
// The command-line options, opened by mcap -h or --help, or from the Debug Panel. Keep in step with
// the Command line table in README.md.
app_help_panel_ui :: () {
if !app.show_help_panel return;
using ImGui;
SetNextWindowSize(imgui_scale(Vector2.{760, 560}), .FirstUseEver);
if app.help_panel_focus { SetNextWindowFocus(); app.help_panel_focus = false; }
push_imgui_window("Command Line", p_open=*app.show_help_panel);
Text("% % [FILE] [options]", APP_NAME, VERSION);
SameLine();
if SmallButton("Copy") {
builder: String_Builder;
builder.allocator = temp;
for APP_COMMAND_LINE_OPTIONS print_to_builder(*builder, "%\t%\n", it.option, it.text);
clipboard_set_text(builder_to_string(*builder,, temp));
toast_show(.Success, "Copied the command-line options");
}
TextColored(app_theme.palette.dim, "-import, -scan, -segment, -stats and -erase still open the window, to show their progress. README.md has the details.");
Spacing();
if BeginTable("options", 2, .Borders | .RowBg | .SizingStretchProp | .ScrollY) {
TableSetupColumn("Option", .WidthFixed);
TableSetupColumn("What it does", .WidthStretch);
TableSetupScrollFreeze(0, 1);
TableHeadersRow();
for APP_COMMAND_LINE_OPTIONS {
TableNextRow();
TableNextColumn();
TextUnformatted(it.option);
TableNextColumn();
TextWrapped("%", it.text);
}
EndTable();
}
}
Command_Line_Option :: struct { option: string; text: string; }
APP_COMMAND_LINE_OPTIONS :: Command_Line_Option.[
.{"FILE (first)", "Opens FILE: an .mcap, image or video. With mcap already running, it opens in view mode beside it."},
.{"-open-import DEST", "With FILE: imports it into the library folder DEST as its Import button does, then exits: 0 once the library has it (imported, or there already), else 1."},
.{"-library FOLDER", "Shows the .mcap files in FOLDER (and its folders) instead of D:\\McapIndex\\mcap."},
.{"-import SOURCE DEST", "Imports SOURCE, a folder or a single file, into the library folder DEST, as the Import window does, then exits: 0, or 1 if any file failed."},
.{"-search QUERY", "Starts with QUERY in the search box."},
.{"-scan", "Scans the library (the chosen OCR model, and labels when the label model is up) once it has loaded, then exits: 0, or 1 if any capture failed."},
.{"-segment", "Segments the images not yet segmented in the library folders set to segment, then exits: 0, or 1 if any image failed."},
.{"-stats", "Logs the Stats window's numbers once the catalog has loaded, then exits; with -search, for the matches."},
.{"-erase WHAT", "Erases ocr, labels, segments or all from every file in the library, then exits (for testing)."},
.{"-record SECONDS", "Records the screen under the cursor that long (no overlay), puts it in the library, then exits: 0, or 1 if it failed."},
.{"-record-region X Y W H", "With -record: records that region instead."},
.{"-record-quit", "With -record: quits while still recording, to check the recording is kept."},
.{"-record-ui-visible", "With -record: leaves the border and toolbar in screenshots, to look at them."},
.{"-cold", "Loads the library as on a cold start (its files dropped from the system's file cache first). With -stats, a startup benchmark."},
.{"-no-catalog-cache", "Reads every capture from its file, as without the catalog cache (for measuring); the cache is still written."},
.{"-catalog-threads N", "Loads the catalog on N threads instead of all the background group's (for measuring)."},
.{"-allow-multiple-instances", "Runs beside another copy instead of bringing that one forward."},
.{"-headless", "No message boxes: a failure is logged and the app exits with 1."},
.{"run_tests", "Runs the test suites (debug builds); with -headless, exits with 0 if all passed."},
.{"-version", "Prints the version, then exits."},
.{"-h, --help", "Opens this window."},
];
frame_time_graph :: () {
using ImGui;
frames := perf.frame_times;
newest := count(frames);
first := newest;
covered: float = 0;
while first > 0 && covered < 1.0 {
first -= 1;
covered += frames[first].delta;
}
max_ms := FRAME_BUDGET_MS;
for first..newest-1 max_ms = max(max_ms, frames[it].work * 1000);
top_label := tprint("%", FF(max_ms, 1));
gutter := imgui_text_size(top_label).x + GetStyle().ItemInnerSpacing.x;
origin := GetCursorScreenPos();
width := GetContentRegionAvail().x;
height := max(GetContentRegionAvail().y, imgui_scale(FRAME_TIME_GRAPH_MIN_HEIGHT));
Dummy(.{width, height});
plot_min := Vector2.{origin.x + gutter, origin.y};
plot_max := Vector2.{origin.x + width, origin.y + height};
plot_w := plot_max.x - plot_min.x;
if plot_w <= 0 return;
draw := GetWindowDrawList();
draw.AddRectFilled(draw, plot_min, plot_max, GetColorU32(.FrameBg));
bars := newest - first;
if bars > 0 {
bar_w := plot_w / cast(float) bars;
gap: float = xx ifx bar_w >= 3 then 1 else 0;
for first..newest-1 {
ms := frames[it].work * 1000;
x0 := plot_min.x + cast(float) (it - first) * bar_w;
y0 := plot_max.y - min(ms / max_ms, 1.0) * height;
draw.AddRectFilled(draw, .{x0, y0}, .{x0 + bar_w - gap, plot_max.y}, GetColorU32(frame_time_color(ms)));
}
}
font_size := GetFontSize();
frame_time_graph_y_label(draw, plot_min.x, plot_min.y, top_label);
frame_time_graph_y_label(draw, plot_min.x, plot_max.y - font_size, "0");
if max_ms > FRAME_BUDGET_MS {
budget_y := plot_max.y - (FRAME_BUDGET_MS / max_ms) * height;
draw.AddLine(draw, .{plot_min.x, budget_y}, .{plot_max.x, budget_y}, GetColorU32(.TextDisabled));
label_y := budget_y - font_size * 0.5;
if label_y > plot_min.y + font_size && label_y < plot_max.y - 2 * font_size {
frame_time_graph_y_label(draw, plot_min.x, label_y, tprint("%", FF(FRAME_BUDGET_MS, 1)));
}
}
if newest > 0 frame_time_graph_live_value(draw, plot_min, plot_max, frames[newest - 1].work * 1000);
}
frame_time_graph_live_value :: (draw: *ImGui.ImDrawList, plot_min: Vector2, plot_max: Vector2, ms: float) {
using ImGui;
push_imgui_font(header_font, scale=FRAME_TIME_LIVE_VALUE_SCALE);
text := tprint("% ms", FF(ms, 2));
size := imgui_text_size(text);
pad := GetStyle().FramePadding;
pos := Vector2.{plot_max.x - pad.x - size.x, plot_min.y + pad.y};
draw.AddText(draw, pos, GetColorU32(.Text, alpha_mul=FRAME_TIME_LIVE_VALUE_ALPHA), text);
}
// Right-aligned against the plot's left edge.
frame_time_graph_y_label :: (draw: *ImGui.ImDrawList, plot_left: float, y: float, text: string) {
x := plot_left - ImGui.GetStyle().ItemInnerSpacing.x - imgui_text_size(text).x;
draw.AddText(draw, .{x, y}, ImGui.GetColorU32(.Text), text);
}
// At or under FRAME_TIME_GREEN_MS is fully green, blending to orange at the budget; over it is red.
frame_time_color :: (ms: float) -> Vector4 {
if ms > FRAME_BUDGET_MS return app_theme.palette.error;
t := clamp((ms - FRAME_TIME_GREEN_MS) / (FRAME_BUDGET_MS - FRAME_TIME_GREEN_MS), 0.0, 1.0);
return lerp(app_theme.palette.success, app_theme.palette.warning, t);
}
FRAME_BUDGET_MS : float : 1000.0 / 60.0;
FRAME_TIME_GREEN_MS : float : 1.0;
FRAME_TIME_GRAPH_MIN_HEIGHT : float : 40.0;
FRAME_TIME_LIVE_VALUE_SCALE : float : 2.0; // of the base font size, in the bold header font
FRAME_TIME_LIVE_VALUE_ALPHA : float : 0.6;