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".
374 lines
13 KiB
Plaintext
374 lines
13 KiB
Plaintext
USING_SDL :: OS == .LINUX || OS == .MACOS;
|
|
USING_WINDOWS :: OS == .WINDOWS;
|
|
|
|
#if !USING_SDL && !USING_WINDOWS
|
|
#assert false "Invalid configuration";
|
|
|
|
// The arena and fixed_arena suites live in modules/Basic/tests and arrive with LOAD_TESTS
|
|
// on the Basic import below.
|
|
#if COMPILE_TESTS {
|
|
#load "test/testing.jai";
|
|
#load "test/logger_tests.jai";
|
|
#load "test/catalog_tests.jai";
|
|
#load "test/ocr_tests.jai";
|
|
#load "test/labels_tests.jai";
|
|
#load "test/import_tests.jai";
|
|
}
|
|
|
|
#load "app/main.jai"; // entry point
|
|
#load "app/app_ui.jai";
|
|
#load "app/viewer.jai";
|
|
#load "app/ocr_ui.jai";
|
|
#load "app/catalog.jai";
|
|
#load "app/stats_ui.jai";
|
|
#load "app/background.jai";
|
|
#load "app/labels_ui.jai";
|
|
#load "app/import.jai";
|
|
#load "app/open_file.jai";
|
|
#load "app/segment_ui.jai";
|
|
#load "app/library_folders.jai";
|
|
#load "app/exit_shutdown.jai";
|
|
#load "app/screen_capture.jai";
|
|
#load "app/screen_record.jai";
|
|
#if OS == .WINDOWS #load "app/screen_capture_win32.jai";
|
|
#if OS == .WINDOWS #load "app/screen_record_win32.jai";
|
|
|
|
#load "base/circle_buffer.jai";
|
|
#load "base/debug.jai";
|
|
#load "base/fonts.jai";
|
|
#load "base/log_queue.jai";
|
|
#load "base/logger.jai";
|
|
#load "base/prune_files.jai";
|
|
|
|
#load "ui/imgui.jai";
|
|
#load "ui/imgui_keyboard.jai";
|
|
#load "ui/imgui_theme.jai";
|
|
#load "ui/imgui_theme_file.jai";
|
|
#load "ui/imgui_theme_registry.jai";
|
|
#load "ui/imgui_theme_editor.jai";
|
|
#load "ui/imgui_toast.jai";
|
|
#load "ui/imgui_exit_overlay.jai";
|
|
|
|
#import "Atomics";
|
|
#import "Basic"()(MEMORY_DEBUGGER=false,ARENA_DEBUG=DEBUG,LOAD_TESTS=COMPILE_TESTS);
|
|
#import "Clipboard";
|
|
#import "String";
|
|
#import "Sort";
|
|
#import "Unicode";
|
|
#import "File";
|
|
#import "Random"; // the noise viewer_overwrite_and_delete writes
|
|
#import "Float16"; // label vectors are stored as 16-bit floats
|
|
#import "File_Utilities";
|
|
#import "Math";
|
|
#import "System";
|
|
#import "Thread";
|
|
#import "Hash_Table";
|
|
#import "Hash"; // fnv1a_hash, for the label list's hash
|
|
Default_Allocator :: #import "Default_Allocator"; // rpmalloc (modules/Default_Allocator), for thread_idle at the workers' waits
|
|
Shared_Channel :: #import "Shared_Memory_Channel";
|
|
#import "stb_image";
|
|
#import "stb_image_resize";
|
|
#import "Capture_File"; // the .mcap format, copied from D:\McapIndex\SnagitConverter
|
|
#import "Image_Decode"; // any image file to pixels: stb_image, and WIC on Windows
|
|
#import "Snagx"; // .snagx captures to .mcap, as SnagitConverter makes them
|
|
#import,dir "../modules/jaison/unicode_utils"; // is_valid_utf8
|
|
#import "OCR";
|
|
#import "Labels";
|
|
#import "Segment";
|
|
#import "Capture_Text";
|
|
#import "Base64";
|
|
#import "onnxruntime";
|
|
#if OS == .WINDOWS #import "Screen_Record"; // screen recording: Desktop Duplication or Windows.Graphics.Capture, to NVENC
|
|
|
|
// Basic's arenas import Secure_Memory at its defaults; this import adds its test suite.
|
|
using Secure_Memory :: #import "Secure_Memory"()(LOAD_TESTS=COMPILE_TESTS);
|
|
|
|
Graphics_Backend :: enum s32 {
|
|
Unknown :: 0;
|
|
DX11;
|
|
Vulkan;
|
|
OpenGL;
|
|
Metal;
|
|
DX12;
|
|
}
|
|
|
|
graphics_backend_from_string :: (name: string) -> Graphics_Backend {
|
|
if name == {
|
|
case "vulkan"; return .Vulkan;
|
|
case "opengl"; return .OpenGL;
|
|
case "metal"; return .Metal;
|
|
case "direct3d11"; return .DX11;
|
|
case "direct3d12"; return .DX12;
|
|
}
|
|
assert(false, "Unknown Graphics_Backend!");
|
|
return .Unknown;
|
|
}
|
|
|
|
Memory_Usage :: struct {
|
|
working_set: s64;
|
|
virtual_memory: s64;
|
|
peak_working_set: s64;
|
|
peak_virtual_memory: s64;
|
|
}
|
|
|
|
perf: Perf;
|
|
Perf :: struct {
|
|
backend: Graphics_Backend;
|
|
renderer_name: string;
|
|
adapter_name: string; // e.g. "NVIDIA GeForce RTX 4070"; empty when the adapter could not be queried
|
|
renderer_is_software: bool;
|
|
time_to_first_frame_ms: float64;
|
|
|
|
// See #WarpFirstStartup in graphics/DX11.jai. hw_device_ready_ms is measured from app.startup_time.
|
|
started_on_warp: bool;
|
|
startup_device_ms: float64; // D3D11CreateDevice for the startup device
|
|
startup_swap_chain_ms: float64; // its swap chain and render target
|
|
hw_device_create_ms: float64;
|
|
hw_device_ready_ms: float64;
|
|
hw_swap_ms: float64;
|
|
|
|
memory_usage: Memory_Usage;
|
|
|
|
frame_times: Circle_Buffer(Frame_Timing);
|
|
}
|
|
|
|
Frame_Timing :: struct {
|
|
work: float32; // seconds from the start of app_frame to just before Present (doesn't include vsync or swapchain waits)
|
|
delta: float32; // io.DeltaTime: wall time since the previous frame, vsync wait included
|
|
}
|
|
|
|
PERF_FRAME_TIME_HISTORY :: 1024;
|
|
|
|
load_hardware_information :: () {
|
|
{ // Get CPU information:
|
|
using cpu_info;
|
|
physical_cpu_count = get_number_of_processors(.ALL_PHYSICAL);
|
|
logical_cpu_count = get_number_of_processors(.ALL_LOGICAL);
|
|
performance_cpu_count = get_number_of_processors(.PERFORMANCE_PHYSICAL);
|
|
}
|
|
#if OS == .WINDOWS windows_get_cpu_information();
|
|
}
|
|
|
|
load_platform_information :: () {
|
|
platform_load_platform_information();
|
|
}
|
|
|
|
cpu_info: CPU_Info;
|
|
CPU_Info :: struct {
|
|
logical_cpu_count: s32;
|
|
physical_cpu_count: s32;
|
|
performance_cpu_count: s32;
|
|
}
|
|
|
|
CPUID_Type :: enum s32 {
|
|
All :: 0; // All Logical CPUs
|
|
Performance :: 1;
|
|
Efficiency :: 2;
|
|
|
|
// Top_Performance :: 64; // Typically two CPUs are marked with this value
|
|
}
|
|
|
|
platform: Platform; // Windows, MacOS, Linux
|
|
|
|
app: App;
|
|
App :: struct {
|
|
frame_count: s64;
|
|
startup_time: Apollo_Time;
|
|
frame_time: Apollo_Time;
|
|
|
|
win_scale: float32;
|
|
minimized_to_tray: bool;
|
|
was_minimized_to_tray: bool; // as of the last frame; main.jai drops stale input when it changes
|
|
vsync_on: bool = true;
|
|
sleep_on_fast_frame: bool = false;
|
|
should_exit: bool = false;
|
|
|
|
show_debug_panel: bool = DEBUG;
|
|
show_logger_panel: bool = DEBUG;
|
|
show_frame_time_graph: bool = DEBUG;
|
|
show_style_editor: bool;
|
|
show_help_panel: bool; // the Command Line window (base/debug.jai): -h, --help, or the Debug Panel
|
|
help_panel_focus: bool; // bring it forward next frame
|
|
minimize_to_tray_this_frame: bool;
|
|
restore_window_this_frame: bool;
|
|
capture_region_this_frame: bool; // the capture_region global hotkey fired: app/screen_capture.jai
|
|
record_region_this_frame: bool; // the record_region global hotkey fired: app/screen_record.jai
|
|
is_master: bool; // Are we the first instance of this app launched?
|
|
allow_multiple_instances: bool; // Skips the single-instance check, so a dev build can run beside an installed one.
|
|
should_exit_early: bool;
|
|
run_tests: bool;
|
|
headless: bool; // -headless (or a no_crash_dialog build): no message boxes on failures, and run_tests exits when done
|
|
exit_code: s32; // the process's exit code when the main loop ends
|
|
library_folder: string; // -library FOLDER: the .mcap folder the viewer shows, in place of VIEWER_FOLDER
|
|
scan_and_exit: bool; // -scan: scan the library with the chosen OCR model once it has loaded, then exit (0, or 1 if any capture failed)
|
|
stats_and_exit: bool; // -stats: log the Statistics window's numbers once the catalog has loaded, then exit
|
|
catalog_threads: s64; // -catalog-threads N: workers loading the catalog at once, in place of all of the background group's (for measuring)
|
|
cold_start: bool;
|
|
no_catalog_cache: bool; // -no-catalog-cache: read every capture from its file, as without the catalog cache (for measuring)
|
|
launch_time: Apollo_Time; // monotonic, when main() started: the [startup] log lines count from it // -cold: load the library as on a cold start, its files dropped from the system's file cache first
|
|
start_search: string; // -search QUERY: the search box's text at startup
|
|
erase_and_exit: string; // -erase ocr|labels|segments|all: erase that from the whole library once it has loaded, then exit
|
|
record_and_exit: float64; // -record SECONDS: record the screen under the cursor that long, put it in the library, then exit (0, or 1 if it failed)
|
|
#if OS == .WINDOWS record_region: RECT; // -record-region X Y W H: -record records that, on the virtual desktop, not the whole screen
|
|
segment_and_exit: bool; // -segment: segment the library folders set to segment once the catalog has loaded, then exit (0, or 1 if any image failed)
|
|
import_source, import_dest: string; // -import SOURCE DEST: import SOURCE into the library folder DEST (app/import.jai), then exit
|
|
open_file: string; // mcap.exe FILE: the file to show (app/open_file.jai); absolute, with / between its folders
|
|
view_mode: bool; // FILE was given while another instance runs: only it is shown, with Import (app/open_file.jai)
|
|
open_import_dest: string; // -open-import DEST: FILE imported into the library folder DEST as its Import button does, then exit (0, or 1 if it failed)
|
|
last_window_restore_time: Apollo_Time;
|
|
|
|
imgui_context: *ImGui.ImGuiContext;
|
|
|
|
write_channel: Shared_Channel.Write_Channel;
|
|
read_channel: Shared_Channel.Read_Channel;
|
|
read_channel_fatal_errors: s32; // the first resets the queue, the second stops polling
|
|
|
|
large_pages_allowed: bool;
|
|
#if USING_WINDOWS {
|
|
is_admin: bool; // running with elevated privileges?
|
|
ui_global_clear_color: [4]float : .[0.45, 0.55, 0.60, 1.00];
|
|
} else #if USING_SDL {
|
|
ui_global_clear_color: SDL_FColor = .{ r=0.45, g=0.55, b=0.60, a=1.0 };
|
|
}
|
|
|
|
program_data_dir: string; // "<os program data>/<APP_FOLDER_NAME>"; see app_program_data_dir()
|
|
machine_name: string;
|
|
username: string;
|
|
args: []string;
|
|
}
|
|
|
|
#if USING_SDL {
|
|
// This uses SDL3 and SDL_GPU
|
|
ImGui :: #import "imgui-sdl";
|
|
|
|
#import "sdl3";
|
|
#import "freetype";
|
|
|
|
// Load the backend(s) you want. This *must* be #load because they might rely on other libraries.
|
|
// In this case, sdl3_gpu3 relies on types from "sdl3", so by importing sdl3 and loading imgui, imgui
|
|
// gets access to the types it needs.
|
|
#load "backends/sdl3_gpu3.jai";
|
|
#load "freetype/freetype.jai";
|
|
|
|
#load "base/platform/sdl.jai";
|
|
|
|
#load "base/platform/platform_posix.jai";
|
|
#if OS == .LINUX {
|
|
#load "base/platform/platform_linux.jai";
|
|
POSIX :: #import "POSIX";
|
|
}
|
|
else #if OS == .MACOS {
|
|
#load "base/platform/platform_macos.jai";
|
|
#load "base/platform/macos_bindings.jai";
|
|
#load "base/platform/macos_global_hotkeys.jai";
|
|
POSIX :: #import "POSIX";
|
|
}
|
|
|
|
APP_TRAY_ICON_PATH :: "images/tray_icon.png";
|
|
}
|
|
|
|
#if USING_WINDOWS {
|
|
// This uses Win32 API directly and DX11 for graphics
|
|
ImGui :: #import "imgui-dx11-win32"()(DEBUG = DEBUG);
|
|
|
|
#import "d3d11";
|
|
#import "d3d_compiler";
|
|
#import "dxgi";
|
|
|
|
#import "Windows";
|
|
#import "Windows_Utf8";
|
|
|
|
#load "base/platform/platform_windows.jai";
|
|
#load "base/platform/win32_bindings.jai";
|
|
#load "base/platform/win32_gdi_bindings.jai";
|
|
#load "graphics/DX11.jai";
|
|
|
|
APP_TRAY_ICON_PATH :: "images/tray_icon.ico";
|
|
}
|
|
|
|
// The folder the app's data, logs and images live in: the executable's, or on macOS the one
|
|
// holding the .app when run from the bundle, so replacing the bundle leaves the data alone. Ends in
|
|
// a separator, as path_strip_filename does. Set by the first call, which the logger makes at startup.
|
|
app_install_directory :: () -> string {
|
|
if !app_install_directory_path {
|
|
directory := path_strip_filename(get_path_of_running_executable(,, temp));
|
|
#if OS == .MACOS {
|
|
BUNDLE_EXECUTABLE_FOLDER :: "/Contents/MacOS/";
|
|
if ends_with(directory, tprint(".app%", BUNDLE_EXECUTABLE_FOLDER)) {
|
|
directory = path_strip_filename(slice(directory, 0, directory.count - BUNDLE_EXECUTABLE_FOLDER.count));
|
|
app_from_app_bundle = true;
|
|
}
|
|
}
|
|
app_install_directory_path = copy_string(directory,, context.default_allocator);
|
|
}
|
|
return app_install_directory_path;
|
|
}
|
|
|
|
app_running_from_app_bundle :: () -> bool {
|
|
app_install_directory();
|
|
return app_from_app_bundle;
|
|
}
|
|
|
|
#scope_file
|
|
app_install_directory_path: string;
|
|
app_from_app_bundle: bool;
|
|
#scope_export
|
|
|
|
get_app_tray_icon_path :: () -> string {
|
|
push_allocator(temp);
|
|
icon_path := tprint("%1%2", app_install_directory(), APP_TRAY_ICON_PATH);
|
|
log("icon_path: %", icon_path);
|
|
return icon_path;
|
|
}
|
|
|
|
APP_LOG_DIRECTORY :: #run -> string {
|
|
return tprint("logs/%", VERSION);
|
|
}
|
|
APP_USER_SETTINGS_FILE_NAME :: "settings.config";
|
|
APP_USER_SETTINGS_VERSION :: 1;
|
|
// A file at any version from this one through APP_USER_SETTINGS_VERSION is readable. Raise
|
|
// APP_USER_SETTINGS_VERSION when an action is added to [hotkeys] or [global_hotkeys]; see
|
|
// hotkeys_missing_lines.
|
|
APP_USER_SETTINGS_OLDEST_READABLE_VERSION :: 1;
|
|
|
|
APP_SHARED_MEMORY_CHANNEL_HEADER_SIZE :: 2; // 1 byte for message-or-done, 1 byte for the checksum.
|
|
App_Shared_Memory_Tag :: enum u8 {
|
|
Done :: 0;
|
|
Bring_To_Front :: 1;
|
|
Open_File :: 2; // the path follows the header: show it (open_file_request)
|
|
Show_Help :: 3; // mcap --help while this one runs: come forward with the Command Line window
|
|
}
|
|
|
|
APP_SHARED_MEMORY_CHANNEL_BYTES :: 4096; // only one page?
|
|
|
|
APP_WINDOW_TITLE :: #run -> string {
|
|
append_debug := ifx DEBUG then " (DEBUG)\0" else " (RELEASE)\0";
|
|
git_info := ifx GIT_BRANCH != "master" && GIT_BRANCH != "main" then
|
|
tprint(" %-%", GIT_BRANCH, GIT_HASH) else tprint(" %", GIT_HASH);
|
|
return tprint("%1 %2%3%4", APP_NAME, VERSION,
|
|
git_info, append_debug);
|
|
}
|
|
|
|
// The window's title, ending in a 0 for the OS: in view mode, the file's name first.
|
|
app_window_title :: () -> string {
|
|
if !app.view_mode return APP_WINDOW_TITLE;
|
|
return tprint("% - % (view)\0", path_filename(app.open_file), APP_NAME);
|
|
}
|
|
|
|
APP_TRAY_TOOLTIP_TEXT :: #run -> string {
|
|
return ifx DEBUG
|
|
tprint("% % (DEBUG)\0", APP_NAME, VERSION)
|
|
else
|
|
tprint("% %\0", APP_NAME, VERSION);
|
|
}
|
|
|
|
#if OS == .LINUX {
|
|
clipboard_set_text :: (text: string) {
|
|
ImGui.SetClipboardText(temp_c_string(text));
|
|
}
|
|
} else {
|
|
clipboard_set_text :: (text: string) {
|
|
os_clipboard_set_text(text);
|
|
}
|
|
}
|