526 lines
24 KiB
Plaintext
526 lines
24 KiB
Plaintext
// The GPU Benchmark window (the gpu-bench branch): runs gpu-bench (tools/gpu_bench) in a process of
|
|
// its own, so the app's engines and memory do not touch its numbers, shows how far it has got, and
|
|
// lays the results files in gpu-bench-results side by side. To compare with another machine, copy
|
|
// its results file into that folder (Open folder).
|
|
|
|
GPU_BENCH_WINDOW :: "GPU Benchmark";
|
|
GPU_BENCH_LOG_LINES :: 2000;
|
|
|
|
gpu_bench_ui: struct {
|
|
window_open: bool;
|
|
run_ocr := true;
|
|
run_labels := true;
|
|
run_segment := true;
|
|
cpu: bool;
|
|
repeats: s32 = 3;
|
|
note: [128] u8;
|
|
|
|
// A run: the reader thread owns the child process and appends what it prints to output.
|
|
running: bool;
|
|
mutex_ready: bool;
|
|
mutex: Mutex;
|
|
thread: Thread;
|
|
command: [..] string; // heap; for the thread
|
|
output: [..] u8; // under mutex; heap
|
|
finished: bool; // under mutex: the child has exited, or could not start
|
|
exit_code: s32; // under mutex
|
|
start_error: string; // under mutex; heap
|
|
cancelled: bool;
|
|
#if OS == .WINDOWS process: HANDLE; // under mutex
|
|
else process: Process;
|
|
started_at: Apollo_Time;
|
|
parsed: s64; // bytes of output made into lines
|
|
log: [..] string; // heap
|
|
scroll_log: bool;
|
|
status: string; // heap: where the run is, or how it ended
|
|
|
|
reports: [..] Bench_Report; // newest first
|
|
shown: [..] bool; // per report: a column in the comparison
|
|
baseline: string; // heap: the path of the report the others are compared with
|
|
loaded: bool;
|
|
};
|
|
|
|
gpu_bench_results_folder :: () -> string {
|
|
return tprint("%gpu-bench-results", path_strip_filename(get_path_of_running_executable()));
|
|
}
|
|
|
|
// The release build, else the debug one; "" when neither is beside the app.
|
|
gpu_bench_executable :: () -> path: string, debug: bool {
|
|
folder := path_strip_filename(get_path_of_running_executable());
|
|
extension := ifx OS == .WINDOWS then ".exe" else "";
|
|
release := tprint("%gpu-bench%", folder, extension);
|
|
if file_exists(release) return release, false;
|
|
debug := tprint("%gpu-bench-debug%", folder, extension);
|
|
if file_exists(debug) return debug, true;
|
|
return "", false;
|
|
}
|
|
|
|
gpu_bench_ui_frame :: () { using ImGui;
|
|
if gpu_bench_ui.running gpu_bench_poll();
|
|
if !gpu_bench_ui.window_open return;
|
|
if !gpu_bench_ui.loaded gpu_bench_load_reports();
|
|
SetNextWindowSize(.{980, 680}, .FirstUseEver);
|
|
if Begin(GPU_BENCH_WINDOW, *gpu_bench_ui.window_open) gpu_bench_window_contents();
|
|
End();
|
|
}
|
|
|
|
gpu_bench_ui_shutdown :: () {
|
|
if gpu_bench_ui.running {
|
|
gpu_bench_cancel();
|
|
thread_is_done(*gpu_bench_ui.thread, -1);
|
|
thread_deinit(*gpu_bench_ui.thread);
|
|
gpu_bench_ui.running = false;
|
|
}
|
|
push_allocator(context.default_allocator);
|
|
for * gpu_bench_ui.reports bench_report_free(it);
|
|
array_free(gpu_bench_ui.reports);
|
|
array_free(gpu_bench_ui.shown);
|
|
for gpu_bench_ui.log free(it);
|
|
array_free(gpu_bench_ui.log);
|
|
array_free(gpu_bench_ui.output);
|
|
for gpu_bench_ui.command free(it);
|
|
array_free(gpu_bench_ui.command);
|
|
free(gpu_bench_ui.status);
|
|
free(gpu_bench_ui.baseline);
|
|
free(gpu_bench_ui.start_error);
|
|
}
|
|
|
|
#scope_file
|
|
|
|
gpu_bench_window_contents :: () { using ImGui;
|
|
TextWrapped("Times each model mcap runs on the GPU (OCR, labels, segmentation) over a fixed corpus of 30 images that every machine has, each checked against its SHA-256 before anything runs. Each model's files and settings are fingerprinted too, so a comparison shows whether it is like for like.");
|
|
exe, debug := gpu_bench_executable();
|
|
|
|
if gpu_bench_ui.running {
|
|
Text("%", gpu_bench_ui.status);
|
|
SameLine();
|
|
TextColored(app_theme.palette.dim, "(% s)", formatFloat(to_float64_seconds(current_time_monotonic() - gpu_bench_ui.started_at), trailing_width = 0));
|
|
SameLine();
|
|
if Button("Cancel") gpu_bench_cancel();
|
|
} else {
|
|
Checkbox("OCR", *gpu_bench_ui.run_ocr);
|
|
SameLine(); Checkbox("Labels", *gpu_bench_ui.run_labels);
|
|
SameLine(); Checkbox("Segmentation", *gpu_bench_ui.run_segment);
|
|
SameLine(); Checkbox("CPU too", *gpu_bench_ui.cpu);
|
|
if IsItemHovered() SetTooltip("Each model on the CPU as well, after the GPU, and the label model's text encoder (a like: search). Slow: minutes for segmentation.");
|
|
SameLine();
|
|
SetNextItemWidth(120);
|
|
SliderInt("Timed passes", *gpu_bench_ui.repeats, 1, 10);
|
|
if IsItemHovered() SetTooltip("Passes over the corpus that are timed, after one untimed warm-up pass; the median is reported");
|
|
SetNextItemWidth(-FLOAT32_MIN);
|
|
InputTextWithHint("##note", "A note kept with the results, such as: plugged in, best performance mode", gpu_bench_ui.note.data, gpu_bench_ui.note.count);
|
|
|
|
reason := "";
|
|
if !exe reason = "gpu-bench is not beside the app: build with jai first.jai - release.";
|
|
else if ocr_ui.thread_busy reason = "A scan or a reading is running on the GPU: let it finish, or pause or cancel it, first.";
|
|
else if !gpu_bench_ui.run_ocr && !gpu_bench_ui.run_labels && !gpu_bench_ui.run_segment reason = "Choose a kind of model to run.";
|
|
if reason BeginDisabled();
|
|
if Button("Run the benchmark") gpu_bench_start(exe);
|
|
if reason EndDisabled();
|
|
if reason { SameLine(); TextColored(app_theme.palette.warning, "%", reason); }
|
|
else if debug { SameLine(); TextColored(app_theme.palette.warning, "Only the debug gpu-bench is built: its CPU work is slow, so build with - release to compare."); }
|
|
else if gpu_bench_ui.status { SameLine(); TextColored(app_theme.palette.dim, "%", gpu_bench_ui.status); }
|
|
TextColored(app_theme.palette.dim, "mcap's own models stay loaded while it runs. For the cleanest numbers, close mcap and run gpu-bench in a terminal; its results land in the same folder.");
|
|
}
|
|
|
|
if CollapsingHeader(temp_c_string(tprint("Output (% lines)###output", gpu_bench_ui.log.count))) {
|
|
if BeginChild("##log", .{0, 220}, .Borders, .HorizontalScrollbar) {
|
|
for gpu_bench_ui.log TextUnformatted(it);
|
|
if gpu_bench_ui.scroll_log { SetScrollHereY(1); gpu_bench_ui.scroll_log = false; }
|
|
}
|
|
EndChild();
|
|
}
|
|
|
|
Separator();
|
|
folder := gpu_bench_results_folder();
|
|
Text("Results (% files)", gpu_bench_ui.reports.count);
|
|
SameLine();
|
|
if SmallButton("Open folder") {
|
|
make_directory_if_it_does_not_exist(folder, recursive = true);
|
|
platform_open_file_in_default_program(folder, folder);
|
|
}
|
|
if IsItemHovered() SetTooltip("% : copy another machine's results files here to compare", folder);
|
|
SameLine();
|
|
if SmallButton("Reload") gpu_bench_load_reports();
|
|
if !gpu_bench_ui.reports.count {
|
|
TextColored(app_theme.palette.dim, "None yet: run the benchmark.");
|
|
return;
|
|
}
|
|
gpu_bench_reports_table();
|
|
Spacing();
|
|
gpu_bench_comparison_table();
|
|
}
|
|
|
|
// The results files: which are shown, and which is the baseline.
|
|
gpu_bench_reports_table :: () { using ImGui;
|
|
height := min(cast(float) gpu_bench_ui.reports.count + 1, 7) * GetFrameHeightWithSpacing();
|
|
if !BeginTable("##reports", 7, .Borders | .RowBg | .SizingFixedFit | .ScrollY, .{0, height}) return;
|
|
TableSetupScrollFreeze(0, 1);
|
|
TableSetupColumn("Show");
|
|
TableSetupColumn("Baseline");
|
|
TableSetupColumn("Date");
|
|
TableSetupColumn("Machine");
|
|
TableSetupColumn("GPU");
|
|
TableSetupColumn("Power");
|
|
TableSetupColumn("Note", .WidthStretch);
|
|
TableHeadersRow();
|
|
for * report, n: gpu_bench_ui.reports {
|
|
PushID(cast(s32) n);
|
|
TableNextRow();
|
|
TableNextColumn(); Checkbox("##show", *gpu_bench_ui.shown[n]);
|
|
TableNextColumn();
|
|
if RadioButton("##baseline", report.path == gpu_bench_ui.baseline) {
|
|
free(gpu_bench_ui.baseline,, context.default_allocator);
|
|
gpu_bench_ui.baseline = copy_string(report.path,, context.default_allocator);
|
|
gpu_bench_ui.shown[n] = true;
|
|
}
|
|
TableNextColumn(); TextUnformatted(bench_header(report, "date"));
|
|
if IsItemHovered() SetTooltip("%", report.path);
|
|
TableNextColumn(); TextUnformatted(bench_header(report, "machine"));
|
|
TableNextColumn(); TextUnformatted(bench_header(report, "gpu"));
|
|
TableNextColumn();
|
|
limit := bench_header(report, "power_limit_w");
|
|
power := bench_header(report, "power");
|
|
TextUnformatted(tprint("%%%", ifx limit then tprint("% W", limit) else "", ifx limit && power then ", " else "", power));
|
|
TableNextColumn(); TextUnformatted(bench_header(report, "note"));
|
|
PopID();
|
|
}
|
|
EndTable();
|
|
}
|
|
|
|
// The shown reports side by side: what each machine is, then each model's time, against the baseline's.
|
|
gpu_bench_comparison_table :: () { using ImGui;
|
|
columns: [..] *Bench_Report;
|
|
columns.allocator = temp;
|
|
baseline: *Bench_Report;
|
|
for * gpu_bench_ui.reports {
|
|
if gpu_bench_ui.shown[it_index] array_add(*columns, it);
|
|
if it.path == gpu_bench_ui.baseline baseline = it;
|
|
}
|
|
if !columns.count { TextColored(app_theme.palette.dim, "Tick Show for the results to compare."); return; }
|
|
if !baseline baseline = columns[0];
|
|
|
|
// Every task and provider any of them ran, in the order they ran.
|
|
Bench_Key :: struct { task, provider: string; }
|
|
keys: [..] Bench_Key;
|
|
keys.allocator = temp;
|
|
for report: columns for report.rows {
|
|
found := false;
|
|
for key: keys if key.task == it.task && key.provider == it.provider found = true;
|
|
if !found array_add(*keys, .{it.task, it.provider});
|
|
}
|
|
|
|
if !BeginTable("##compare", cast(s32) columns.count + 1, .Borders | .RowBg | .SizingFixedFit | .ScrollX | .ScrollY, .{0, -FLOAT32_MIN}) return;
|
|
TableSetupScrollFreeze(1, 1);
|
|
TableSetupColumn("");
|
|
for columns TableSetupColumn(temp_c_string(tprint("%##%", bench_header(it, "machine"), it_index)));
|
|
TableHeadersRow();
|
|
|
|
header_row :: (label: string, key: string, tooltip := "") #expand {
|
|
TableNextRow();
|
|
TableNextColumn(); TextColored(app_theme.palette.dim, "%", label);
|
|
if tooltip && IsItemHovered() SetTooltip("%", tooltip);
|
|
for columns {
|
|
TableNextColumn();
|
|
value := bench_header(it, key);
|
|
if it != baseline && value != bench_header(baseline, key) TextColored(app_theme.palette.accent, "%", value);
|
|
else TextUnformatted(value);
|
|
}
|
|
}
|
|
header_row("GPU", "gpu");
|
|
header_row("Memory", "vram_gb", "GB of GPU memory");
|
|
header_row("Power limit", "power_limit_w", "The board's enforced limit in watts: a laptop's GPU is often far below the desktop part's");
|
|
header_row("Power", "power");
|
|
header_row("Driver", "driver");
|
|
header_row("cuDNN", "cudnn");
|
|
header_row("onnxruntime", "onnxruntime");
|
|
header_row("CPU", "cpu");
|
|
header_row("Date", "date");
|
|
header_row("Note", "note");
|
|
|
|
// The corpus: different images make every number not comparable.
|
|
TableNextRow();
|
|
TableNextColumn(); TextColored(app_theme.palette.dim, "Corpus");
|
|
for columns {
|
|
TableNextColumn();
|
|
corpus := bench_header(it, "corpus");
|
|
if corpus == bench_header(baseline, "corpus") TextColored(app_theme.palette.success, "same (%)", slice(corpus, 0, min(corpus.count, 12)));
|
|
else TextColored(app_theme.palette.error, "DIFFERENT: not comparable");
|
|
}
|
|
|
|
for key: keys {
|
|
TableNextRow();
|
|
TableNextColumn();
|
|
TextUnformatted(tprint("% %", key.task, ifx key.provider == "CUDA" then "(GPU)" else "(CPU)"));
|
|
base_row := bench_row(baseline, key.task, key.provider);
|
|
base_ms := ifx base_row then string_to_float64(bench_field(base_row, "ms")) else 0;
|
|
for report: columns {
|
|
TableNextColumn();
|
|
row := bench_row(report, key.task, key.provider);
|
|
if !row {
|
|
error := bench_header(report, tprint("error % %", key.task, key.provider));
|
|
if !error error = bench_header(report, tprint("error % CPU", key.task)); // a GPU run that fell back
|
|
if error { TextColored(app_theme.palette.error, "failed"); if IsItemHovered() SetTooltip("%", error); }
|
|
else TextColored(app_theme.palette.dim, "-");
|
|
continue;
|
|
}
|
|
ms := string_to_float64(bench_field(row, "ms"));
|
|
unit := bench_field(row, "unit");
|
|
same_model := bench_header(report, tprint("model %", key.task)) == bench_header(baseline, tprint("model %", key.task));
|
|
text := tprint("% ms/%", formatFloat(ms, trailing_width = ifx ms < 10 then 2 else 1), unit);
|
|
if report != baseline && base_ms > 0 && ms > 0 {
|
|
ratio := base_ms / ms; // above 1: faster than the baseline
|
|
colour := ifx ratio > 1.05 then app_theme.palette.success else ifx ratio < 0.95 then app_theme.palette.warning else app_theme.palette.neutral;
|
|
TextColored(colour, "% (%x)%", text, formatFloat(ratio, trailing_width = 2), ifx same_model then "" else " *");
|
|
} else {
|
|
TextUnformatted(tprint("%%", text, ifx same_model then "" else " *"));
|
|
}
|
|
if IsItemHovered() {
|
|
builder: String_Builder;
|
|
builder.allocator = temp;
|
|
if !same_model append(*builder, "* Different model files or models.config settings from the baseline's: not like for like.\n\n");
|
|
print_to_builder(*builder, "% on %: % %s per second", key.task, report.path, bench_field(row, "per_s"), unit);
|
|
mp := bench_field(row, "mp_per_s");
|
|
if mp print_to_builder(*builder, ", % megapixels per second", mp);
|
|
print_to_builder(*builder, "\nmedian of the timed passes; best % ms, worst % ms\n", bench_field(row, "best_ms"), bench_field(row, "worst_ms"));
|
|
print_to_builder(*builder, "loading % s, the first % % s, batch %\n", bench_field(row, "load_s"), unit, bench_field(row, "first_s"), bench_field(row, "batch"));
|
|
vram := bench_field(row, "vram_mb");
|
|
if vram print_to_builder(*builder, "GPU: % MB more memory at the peak, % percent busy, % W, % MHz, up to % C\n", vram,
|
|
bench_field(row, "gpu_util"), bench_field(row, "power_w"), bench_field(row, "sm_mhz"), bench_field(row, "temp_c"));
|
|
stages := bench_field(row, "stages");
|
|
if stages print_to_builder(*builder, "ms per %, summed over threads: %\n", unit, stages);
|
|
print_to_builder(*builder, "found %", bench_field(row, "output"));
|
|
if report != baseline && base_row {
|
|
same_output := bench_field(row, "digest") == bench_field(base_row, "digest");
|
|
print_to_builder(*builder, ", % the baseline's", ifx same_output then "the same as" else "not quite the same as (GPUs round differently)");
|
|
}
|
|
SetTooltip("%", builder_to_string(*builder,, temp));
|
|
}
|
|
}
|
|
}
|
|
EndTable();
|
|
}
|
|
|
|
gpu_bench_load_reports :: () {
|
|
push_allocator(context.default_allocator);
|
|
for * gpu_bench_ui.reports bench_report_free(it);
|
|
array_reset(*gpu_bench_ui.reports);
|
|
array_reset(*gpu_bench_ui.shown);
|
|
gpu_bench_ui.loaded = true;
|
|
folder := gpu_bench_results_folder();
|
|
if !file_exists(folder) return;
|
|
for file_list(folder,, temp) {
|
|
if !ends_with_nocase(it, ".txt") continue;
|
|
text, ok := read_entire_file(it, log_errors = false,, temp);
|
|
if !ok continue;
|
|
report, parsed, error := bench_parse(text, it);
|
|
if !parsed { log_error("[gpu-bench] %", error); bench_report_free(*report); continue; }
|
|
array_add(*gpu_bench_ui.reports, report);
|
|
}
|
|
quick_sort(gpu_bench_ui.reports, (a: Bench_Report, b: Bench_Report) -> s64 { return compare(bench_header(*b, "date"), bench_header(*a, "date")); });
|
|
array_resize(*gpu_bench_ui.shown, gpu_bench_ui.reports.count);
|
|
for * gpu_bench_ui.shown it.* = true;
|
|
found := false;
|
|
for gpu_bench_ui.reports if it.path == gpu_bench_ui.baseline found = true;
|
|
if !found && gpu_bench_ui.reports.count {
|
|
free(gpu_bench_ui.baseline);
|
|
gpu_bench_ui.baseline = copy_string(gpu_bench_ui.reports[gpu_bench_ui.reports.count - 1].path); // the oldest: what later runs are measured against
|
|
}
|
|
}
|
|
|
|
gpu_bench_start :: (exe: string) {
|
|
push_allocator(context.default_allocator);
|
|
if !gpu_bench_ui.mutex_ready { init(*gpu_bench_ui.mutex); gpu_bench_ui.mutex_ready = true; }
|
|
for gpu_bench_ui.command free(it);
|
|
array_reset(*gpu_bench_ui.command);
|
|
add :: (s: string) #expand { array_add(*gpu_bench_ui.command, copy_string(s)); }
|
|
add(exe);
|
|
add("-progress");
|
|
add("-repeats"); add(tprint("%", gpu_bench_ui.repeats));
|
|
add("-out"); add(gpu_bench_results_folder());
|
|
only: [..] string;
|
|
only.allocator = temp;
|
|
if gpu_bench_ui.run_ocr array_add(*only, "ocr");
|
|
if gpu_bench_ui.run_labels array_add(*only, "labels");
|
|
if gpu_bench_ui.run_segment array_add(*only, "segment");
|
|
add("-only"); add(join(..only, ",",, temp));
|
|
if gpu_bench_ui.cpu add("-cpu");
|
|
note := trim(to_string(gpu_bench_ui.note.data));
|
|
if note { add("-note"); add(note); }
|
|
|
|
for gpu_bench_ui.log free(it);
|
|
array_reset(*gpu_bench_ui.log);
|
|
array_reset(*gpu_bench_ui.output);
|
|
free(gpu_bench_ui.start_error);
|
|
gpu_bench_ui.start_error = "";
|
|
gpu_bench_ui.finished = false;
|
|
gpu_bench_ui.cancelled = false;
|
|
gpu_bench_ui.exit_code = 0;
|
|
gpu_bench_ui.parsed = 0;
|
|
gpu_bench_set_status("Starting...");
|
|
gpu_bench_ui.started_at = current_time_monotonic();
|
|
thread_init(*gpu_bench_ui.thread, gpu_bench_thread_proc, name = "gpu-bench");
|
|
thread_start(*gpu_bench_ui.thread);
|
|
gpu_bench_ui.running = true;
|
|
log("[gpu-bench] Running %", join(..gpu_bench_ui.command, " ",, temp));
|
|
}
|
|
|
|
gpu_bench_cancel :: () {
|
|
lock(*gpu_bench_ui.mutex);
|
|
gpu_bench_ui.cancelled = true;
|
|
#if OS == .WINDOWS { if gpu_bench_ui.process TerminateProcess(gpu_bench_ui.process, 1); }
|
|
else kill_process(*gpu_bench_ui.process);
|
|
unlock(*gpu_bench_ui.mutex);
|
|
}
|
|
|
|
// What the child printed since the last frame, made into lines; and the end of the run.
|
|
gpu_bench_poll :: () {
|
|
push_allocator(context.default_allocator);
|
|
lock(*gpu_bench_ui.mutex);
|
|
finished := gpu_bench_ui.finished;
|
|
exit_code := gpu_bench_ui.exit_code;
|
|
new_text := copy_string(cast(string) array_view(gpu_bench_ui.output, gpu_bench_ui.parsed, gpu_bench_ui.output.count - gpu_bench_ui.parsed),, temp);
|
|
start_error := copy_string(gpu_bench_ui.start_error,, temp);
|
|
unlock(*gpu_bench_ui.mutex);
|
|
|
|
// Whole lines only, until the end.
|
|
end := find_index_from_right(new_text, #char "\n");
|
|
if finished end = new_text.count;
|
|
if end > 0 {
|
|
gpu_bench_ui.parsed += end;
|
|
for raw: split(slice(new_text, 0, end), "\n",, temp) {
|
|
line := trim_right(raw, "\r");
|
|
if !line.count continue;
|
|
if line[0] == 0x1b continue; // onnxruntime's coloured warnings
|
|
if begins_with(line, "@progress ") { gpu_bench_set_status(gpu_bench_progress_words(advance(line, 10))); continue; }
|
|
if begins_with(line, "@results ") continue;
|
|
array_add(*gpu_bench_ui.log, copy_string(line));
|
|
gpu_bench_ui.scroll_log = true;
|
|
}
|
|
while gpu_bench_ui.log.count > GPU_BENCH_LOG_LINES {
|
|
free(gpu_bench_ui.log[0]);
|
|
array_ordered_remove_by_index(*gpu_bench_ui.log, 0);
|
|
}
|
|
}
|
|
if !finished return;
|
|
|
|
thread_is_done(*gpu_bench_ui.thread, -1);
|
|
thread_deinit(*gpu_bench_ui.thread);
|
|
gpu_bench_ui.running = false;
|
|
seconds := formatFloat(to_float64_seconds(current_time_monotonic() - gpu_bench_ui.started_at), trailing_width = 0);
|
|
if start_error gpu_bench_set_status(start_error);
|
|
else if gpu_bench_ui.cancelled gpu_bench_set_status("Cancelled.");
|
|
else if exit_code == 0 gpu_bench_set_status(tprint("Done in % s.", seconds));
|
|
else gpu_bench_set_status(tprint("gpu-bench failed (exit code %) after % s: Output says why.", exit_code, seconds));
|
|
log("[gpu-bench] %", gpu_bench_ui.status);
|
|
gpu_bench_load_reports();
|
|
}
|
|
|
|
// "@progress ocr/ppocr-v5-mobile CUDA timing 2 3" in words.
|
|
gpu_bench_progress_words :: (text: string) -> string {
|
|
parts := split(text, " ",, temp);
|
|
if parts.count < 5 return text;
|
|
task, provider, phase, done, total := parts[0], parts[1], parts[2], parts[3], parts[4];
|
|
on := ifx provider == "CUDA" then "on the GPU" else ifx provider == "CPU" then "on the CPU" else "";
|
|
if task == "corpus" return tprint("The corpus: % %...", phase, ifx done != "0" then tprint("image %", done) else "");
|
|
if task == "hashing" return tprint("Fingerprinting %: % (% of %)", provider, phase, done, total);
|
|
if phase == {
|
|
case "loading"; return tprint("% %: loading the model...", task, on);
|
|
case "first"; return tprint("% %: the first image...", task, on);
|
|
case "warm-up"; return tprint("% %: warm-up pass % of %...", task, on, done, total);
|
|
case "timing"; return tprint("% %: timed pass % of %...", task, on, done, total);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
gpu_bench_set_status :: (text: string) {
|
|
free(gpu_bench_ui.status,, context.default_allocator);
|
|
gpu_bench_ui.status = copy_string(text,, context.default_allocator);
|
|
}
|
|
|
|
// The reader thread: starts gpu-bench, takes what it prints until it exits.
|
|
gpu_bench_thread_proc :: (thread: *Thread) -> s64 {
|
|
append_output :: (data: *u8, count: s64) {
|
|
lock(*gpu_bench_ui.mutex);
|
|
old := gpu_bench_ui.output.count;
|
|
array_resize(*gpu_bench_ui.output, old + count, initialize = false,, context.default_allocator);
|
|
memcpy(gpu_bench_ui.output.data + old, data, count);
|
|
unlock(*gpu_bench_ui.mutex);
|
|
}
|
|
finish :: (exit_code: s32, error := "") {
|
|
lock(*gpu_bench_ui.mutex);
|
|
gpu_bench_ui.exit_code = exit_code;
|
|
if error gpu_bench_ui.start_error = copy_string(error,, context.default_allocator);
|
|
gpu_bench_ui.finished = true;
|
|
unlock(*gpu_bench_ui.mutex);
|
|
}
|
|
buffer: [4096] u8;
|
|
|
|
#if OS == .WINDOWS {
|
|
// Not the Process module: from a GUI program, it would open a console window for gpu-bench.
|
|
attributes: SECURITY_ATTRIBUTES;
|
|
attributes.nLength = size_of(SECURITY_ATTRIBUTES);
|
|
attributes.bInheritHandle = 1;
|
|
read_end, write_end: HANDLE;
|
|
if !CreatePipe(*read_end, *write_end, *attributes, 0) { finish(-1, tprint("Could not start gpu-bench: no pipe (Windows error %).", GetLastError())); return 0; }
|
|
SetHandleInformation(read_end, HANDLE_FLAG_INHERIT, 0);
|
|
startup: STARTUPINFOW;
|
|
startup.cb = size_of(STARTUPINFOW);
|
|
startup.dwFlags = STARTF_USESTDHANDLES;
|
|
startup.hStdOutput = write_end;
|
|
startup.hStdError = write_end;
|
|
builder: String_Builder;
|
|
builder.allocator = temp;
|
|
for gpu_bench_ui.command { if it_index append(*builder, " "); append(*builder, windows_quote_argument(it)); }
|
|
command_line := builder_to_string(*builder,, temp);
|
|
info: PROCESS_INFORMATION;
|
|
working := path_strip_filename(gpu_bench_ui.command[0]);
|
|
started := CreateProcessW(null, utf8_to_wide(command_line,, temp), null, null, 1, CREATE_NO_WINDOW, null, utf8_to_wide(working,, temp), *startup, *info);
|
|
CloseHandle(write_end); // the child's copy is the only one left: reading ends when it exits
|
|
if !started {
|
|
error := GetLastError();
|
|
CloseHandle(read_end);
|
|
finish(-1, tprint("Could not start % (Windows error %).", gpu_bench_ui.command[0], error));
|
|
return 0;
|
|
}
|
|
CloseHandle(info.hThread);
|
|
lock(*gpu_bench_ui.mutex);
|
|
gpu_bench_ui.process = info.hProcess;
|
|
cancelled := gpu_bench_ui.cancelled;
|
|
unlock(*gpu_bench_ui.mutex);
|
|
if cancelled TerminateProcess(info.hProcess, 1);
|
|
while true {
|
|
read: u32;
|
|
if !ReadFile(read_end, buffer.data, buffer.count, *read) || !read break;
|
|
append_output(buffer.data, read);
|
|
}
|
|
CloseHandle(read_end);
|
|
WaitForSingleObject(info.hProcess, INFINITE);
|
|
exit_code: u32;
|
|
GetExitCodeProcess(info.hProcess, *exit_code);
|
|
lock(*gpu_bench_ui.mutex);
|
|
gpu_bench_ui.process = null;
|
|
unlock(*gpu_bench_ui.mutex);
|
|
CloseHandle(info.hProcess);
|
|
finish(cast,no_check(s32) exit_code);
|
|
} else {
|
|
errors: [4096] u8;
|
|
if !create_process(*gpu_bench_ui.process, ..gpu_bench_ui.command, capture_and_return_output = true) {
|
|
finish(-1, tprint("Could not start %.", gpu_bench_ui.command[0]));
|
|
return 0;
|
|
}
|
|
while true {
|
|
ok, output_bytes, error_bytes := read_from_process(*gpu_bench_ui.process, buffer, errors, -1);
|
|
if output_bytes append_output(buffer.data, output_bytes);
|
|
if error_bytes append_output(errors.data, error_bytes);
|
|
if !ok || (gpu_bench_ui.process.output.eof && gpu_bench_ui.process.error.eof) break;
|
|
}
|
|
_, result := get_process_result(*gpu_bench_ui.process, -1);
|
|
deinit(*gpu_bench_ui.process);
|
|
finish(cast(s32) result.exit_code);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#import "Process";
|