mcap/src/app/main.jai

777 lines
31 KiB
Plaintext

main :: () {
using platform; using ImGui;
// #Startup: Program Startup
app.launch_time = current_time_monotonic();
app.args = get_command_line_arguments();
print_version_and_exit := array_find(app.args, "-version");
if print_version_and_exit {
print("Version: %-%-%, built on %", VERSION, GIT_BRANCH, GIT_HASH, BUILD_DATE);
return;
}
load_hardware_information();
load_platform_information();
args_combined := join(..app.args, " ");
app.run_tests = array_find(app.args, "run_tests");
app.headless = array_find(app.args, "-headless") || NO_CRASH_DIALOG;
runtime_support_headless = app.headless;
app.allow_multiple_instances = array_find(app.args, "-allow-multiple-instances");
for app.args if it == "-library" && it_index + 1 < app.args.count app.library_folder = library_folder_normalize(app.args[it_index + 1]);
app.scan_and_exit = array_find(app.args, "-scan");
app.segment_and_exit = array_find(app.args, "-segment");
for app.args if it == "-record" && it_index + 1 < app.args.count app.record_and_exit = max(string_to_float64(app.args[it_index + 1]), 1.0);
#if OS == .WINDOWS for app.args if it == "-record-region" && it_index + 4 < app.args.count {
x, y := cast(s32) string_to_int(app.args[it_index + 1]), cast(s32) string_to_int(app.args[it_index + 2]);
app.record_region = .{x, y, x + cast(s32) string_to_int(app.args[it_index + 3]), y + cast(s32) string_to_int(app.args[it_index + 4])};
}
app.stats_and_exit = array_find(app.args, "-stats");
app.cold_start = array_find(app.args, "-cold");
app.no_catalog_cache = array_find(app.args, "-no-catalog-cache");
for app.args if it == "-catalog-threads" && it_index + 1 < app.args.count app.catalog_threads = string_to_int(app.args[it_index + 1]);
for app.args if it == "-search" && it_index + 1 < app.args.count app.start_search = app.args[it_index + 1];
for app.args if it == "-erase" && it_index + 1 < app.args.count app.erase_and_exit = app.args[it_index + 1];
for app.args if it == "-import" && it_index + 2 < app.args.count { app.import_source = app.args[it_index + 1]; app.import_dest = app.args[it_index + 2]; }
// mcap.exe FILE, as Explorer's Open with passes it: an .mcap, or an image or video (app/open_file.jai).
if app.args.count > 1 && app.args[1].count && app.args[1][0] != #char "-" && app.args[1] != "run_tests" {
app.open_file = open_file_normalize(app.args[1]);
}
for app.args if it == "-open-import" && it_index + 1 < app.args.count app.open_import_dest = app.args[it_index + 1];
for app.args if it == "-h" || it == "--help" || it == "-help" { app.show_help_panel = true; app.help_panel_focus = true; } // the Command Line window (base/debug.jai)
defer report_memory_leaks();
channel_key := app_shared_memory_channel_key();
defer free(channel_key);
// 1. Check file lock on POSIX machines:
app_instance_running : bool;
#if USING_SDL {
lock_path := app_instance_lock_path();
lock_fd: s32 = -1;
if lock_path {
// 600 in base 8 is 0x180. O_NOFOLLOW, so a planted symlink is not truncated below.
lock_fd = POSIX.open(temp_c_string(lock_path), POSIX.O_CREAT|POSIX.O_RDWR|POSIX.O_CLOEXEC|POSIX.O_NOFOLLOW, 0x180);
if lock_fd < 0 log_error("Failed to open the instance lock %: errno %", lock_path, POSIX.errno());
} else {
log_error("No per-user directory is available for the instance lock.");
}
defer if lock_fd >= 0 POSIX.close(lock_fd);
lock_taken := false;
if lock_fd >= 0 {
// Set up the flock structure for an exclusive write lock:
fl: POSIX.flock;
fl.l_type = POSIX.F_WRLCK; // exclusive write lock
fl.l_whence = POSIX.SEEK_SET;
fl.l_start = 0; // lock from start of file
fl.l_len = 0; // lock until EOF
// Try to lock the file. F_SETLK returns -1 immediately if locked.
if POSIX.fcntl(lock_fd, POSIX.F_SETLK, *fl) == -1 {
if (POSIX.errno() == POSIX.EACCES || POSIX.errno() == POSIX.EAGAIN) {
log("app_instance_running already!");
app_instance_running = true;
} else {
log_error("fcntl on the instance lock failed: errno %", POSIX.errno());
}
} else {
lock_taken = true;
}
}
if !lock_taken && !app_instance_running {
log_error("Could not take the instance lock; running without single-instance support.");
app.allow_multiple_instances = true;
}
if lock_taken {
// remove stale reference if needed:
POSIX.shm_unlink(temp_c_string(channel_key));
// Truncate the file and write PID:
if POSIX.ftruncate(lock_fd, 0) == 0 {
pid := POSIX.getpid();
pid_str := tprint("%\n", pid);
POSIX.write(lock_fd, pid_str.data, cast(u64)pid_str.count);
}
}
}
if !app.allow_multiple_instances {
// #Startup:#shared_memory_channel:bringToFront
// One primitive decides which instance is master: the lock on POSIX, and the creation of the
// mapping on Windows. Whoever loses only ever connects, retrying while the winner may still
// be setting its channel up, and then exits.
another_instance_owns_channel := false;
#if USING_SDL another_instance_owns_channel = app_instance_running;
if !another_instance_owns_channel && app_running_instance_reachable(channel_key) another_instance_owns_channel = true;
if !another_instance_owns_channel {
read_channel_success, read_channel, already_exists :=
Shared_Channel.reader_create_channel(channel_key, APP_SHARED_MEMORY_CHANNEL_BYTES);
if read_channel_success {
app.read_channel = read_channel;
app.is_master = true;
} else if already_exists {
another_instance_owns_channel = true;
} else {
// Not fatal: this instance runs without the channel, so later launches will not bring it forward.
log_error("Failed to create the shared memory channel; continuing without single-instance support.");
}
}
if another_instance_owns_channel && app.open_file {
// A file opened while mcap runs: shown here, on its own, in view mode (app/open_file.jai).
app.view_mode = true;
app.show_debug_panel = false;
app.show_logger_panel = false;
app.show_frame_time_graph = false;
} else if another_instance_owns_channel && app.show_help_panel {
// mcap --help while mcap runs: that one comes forward with the Command Line window.
if !app_send_to_running_instance(channel_key, 20, .Show_Help) {
log_error("Another instance is running, but its shared memory channel could not be reached (it may be running as administrator); exiting.");
}
return;
} else if another_instance_owns_channel {
if !app_bring_running_instance_to_front(channel_key, attempts=20) {
log_error("Another instance is running, but its shared memory channel could not be reached (it may be running as administrator); exiting.");
}
return;
}
}
defer if app.is_master Shared_Channel.reader_release_channel(*app.read_channel, channel_key);
crash_report_folder_name = APP_FOLDER_NAME;
init_logger(); defer close_logger();
log(" --- /STARTUP INFO\\ --- ");
log(" - APP_NAME: %", APP_NAME);
log(" - VERSION: %", VERSION);
log(" - DEBUG: %", DEBUG);
log(" - BUILD_DATE: %", BUILD_DATE);
log(" - GIT_BRANCH: %", GIT_BRANCH);
log(" - GIT_HASH: %", GIT_HASH);
log(" - COMPILE_ARGS: %", COMPILE_ARGS);
log(" - COMMAND_LINE_ARGUMENTS: %", args_combined);
if app.view_mode log(" - VIEW MODE: another instance is running; showing % on its own.", app.open_file);
log(" --- \\STARTUP INFO/ --- ");
app.startup_time = current_time_monotonic();
resize(*perf.frame_times, PERF_FRAME_TIME_HISTORY,, context.default_allocator);
timing_set_tick_frequency();
if app.run_tests {
#if COMPILE_TESTS {
// Headless, a failed suite is counted and reported in the exit code rather than asserted on.
testing_init(assert_on_fail = DEBUG && !app.headless, log_successful_tests=DEBUG);
run_all_tests();
} else {
log_error("run_tests was passed, but this build was compiled without tests. Rebuild with the compile_tests argument to include them.");
}
}
#if USING_WINDOWS {
windows_check_if_we_are_running_as_administrator();
if app.should_exit_early then return;
windows_enable_large_pages_if_possible();
}
#if OS == .MACOS {
init_mac_app();
}
app.machine_name = copy_string(get_machinename());
app.username = copy_string(get_username());
log("Current PC name: %, username: %", app.machine_name, app.username);
settings_write_back := app_load_user_settings_file() && !app.view_mode; // the running instance owns the file
logger_set_event_log_capacity(app_user_settings.event_log_capacity);
library_folder_choose();
log("Library: %", app.library_folder);
// #Startup:CreateWindow
#if USING_SDL {
// Keep freetype from being removed by the compiler
FT_Error_String(0);
// Setup window and gpu using the SDL3_GPU3 backend.
// This is based on the 'example_sdl3_sdlgpu3' Dear ImGui example, which you can find on their repo.
success := SDL_Init(SDL_INIT_VIDEO);
#if OS == .MACOS { // By default Cmd+W closes the application if the last window exits, so we need to tell SDL to NOT do that.
SDL_SetHint(SDL_HINT_QUIT_ON_LAST_WINDOW_CLOSE, "0");
}
ok_or_exit(success, "failed to init sdl3 with error = %", consume_sdl_error());
display := sdl_largest_display();
app.win_scale = SDL_GetDisplayContentScale(display);
if app.win_scale <= 0 app.win_scale = 1.0; // 0 when SDL cannot tell, as on some Linux setups
mode := SDL_GetCurrentDisplayMode(display);
w, h := sdl_default_window_size(mode);
window_flags : SDL_WindowFlags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY;
platform.window = SDL_CreateWindow(app_window_title().data, w, h, window_flags);
ok_or_exit(platform.window != null, "failed to create window with error = %", consume_sdl_error());
centered := SDL_WINDOWPOS_CENTERED_DISPLAY(display);
SDL_SetWindowPosition(platform.window, xx centered, xx centered);
sdl_set_app_icon();
#if OS == .MACOS {
SDL_AddEventWatch(sdl_live_resize_watch, null);
}
SDL_ShowWindow(platform.window);
} // #if USING_SDL
#if USING_WINDOWS {
ImGui.ImplWin32_EnableDpiAwareness();
app.win_scale = ImGui.ImplWin32_GetDpiScaleForMonitor(MonitorFromPoint({0,0}, MONITOR_DEFAULTTOPRIMARY));
lpszClassName :: #run tprint("%WindowClass", APP_NAME);
wc: WNDCLASSEXW;
wc.cbSize = size_of(WNDCLASSEXW);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = xx WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = null;
wc.hIcon = LoadImageW(null, utf8_to_wide(get_app_tray_icon_path(),,temp), IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
wc.hCursor = LoadCursorW(null, IDC_ARROW);
wc.hbrBackground = null;
wc.lpszMenuName = null;
wc.lpszClassName = utf8_to_wide(lpszClassName,, context.default_allocator);
if RegisterClassExW(*wc) == 0 {
log("RegisterClassExW failed\n");
return;
}
defer {
UnregisterClassW(wc.lpszClassName, wc.hInstance);
free(wc.lpszClassName,, context.default_allocator);
}
// Get primary display height and width
window_x, window_y, win_width, win_height := platform_get_centered_window_dimensions(open_on_biggest=true);
log("Window Location: x: %, y: %, size: %x% px", window_x, window_y, win_width, win_height);
hwnd = CreateWindowExW(WS_EX_APPWINDOW,
utf8_to_wide(lpszClassName,,temp),
utf8_to_wide(app_window_title(),,temp),
WS_OVERLAPPEDWINDOW,
window_x, window_y, win_width, win_height,
null, null, null, null);
if !hwnd {
print("CreateWindowExW failed\n");
return;
}
defer DestroyWindow(hwnd);
} // #if USING_WINDOWS
// #Startup:Tray #Startup:GlobalHotkeys
#if USING_SDL {
posix_install_exit_signal_handlers();
#if OS == .LINUX {
linux_platform_setup();
}
}
#if USING_WINDOWS { // :Win32_Initialize_Extended
// View mode has no tray: closing its window ends it.
if !app.view_mode { Win32_Set_Tray_Icon(); Win32_Hide_Tray_Icon(); }
defer if !app.view_mode Win32_Hide_Tray_Icon(); // exiting from the tray would otherwise leave the icon behind
}
// [global_hotkeys] was read with the settings, before there was a window to register them for.
// The running instance has them; view mode leaves them alone.
if !app.view_mode global_hotkeys_apply();
defer platform_unregister_global_hotkeys();
// #Startup:Graphics
#if USING_SDL {
new_device := SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_METALLIB, DEBUG, null);
ok_or_exit(new_device != null, "failed to create gpu device with error = %", consume_sdl_error());
platform.gpu_device = new_device;
success = SDL_ClaimWindowForGPUDevice(gpu_device, platform.window);
ok_or_exit(success, "failed to claim window with title '%' for gpu device with error = %", to_string(SDL_GetWindowTitle(platform.window)), consume_sdl_error());
success = SDL_SetGPUSwapchainParameters(gpu_device, platform.window, .SDR, .VSYNC);
ok_or_exit(success, "failed to set swapchain parameters with error = %", consume_sdl_error());
perf.renderer_name = copy_string(to_string(SDL_GetGPUDeviceDriver(gpu_device)));
log("SDL renderer_name: %", perf.renderer_name);
}
#if USING_WINDOWS {
// #Startup:Graphics See #WarpFirstStartup in graphics/DX11.jai.
if !D3D11_InitDevice(hwnd, .WARP) {
log_error("Could not create a D3D11 device of any kind. Exiting.");
return;
}
perf.renderer_name = "direct3d11";
perf.started_on_warp = (g_driver_type == .WARP);
log("[D3D11] Renderer up on '%' (%): device % ms, swap chain % ms.", perf.adapter_name, g_driver_type,
FF(perf.startup_device_ms, 1), FF(perf.startup_swap_chain_ms, 1));
// Started hardware device thread after WARP initialied, as in the reference implementation: https://gist.github.com/thomasklemenc/2d4d43001a977a3dafe4de73efc348da
// Running both simultaneously causes contention with the loader lock and DXGI's internal locks, and the resulting init time is 100ms+
if perf.started_on_warp then d3d11_start_hardware_device_thread();
defer d3d11_finish_hardware_device_thread();
ShowWindow(hwnd, SW_SHOWDEFAULT);
UpdateWindow(hwnd);
// Set dark titlebar:
dark_mode: s64 = 1;
DwmSetWindowAttribute(hwnd, .USE_IMMERSIVE_DARK_MODE, *dark_mode, size_of(s64));
// A hacky workaround to display the dark title bar properly. Nothing else we've tried worked (credit: focus-editor)
ShowWindow(hwnd, SW_HIDE);
ShowWindow(hwnd, SW_SHOW);
}
perf.backend = graphics_backend_from_string(perf.renderer_name);
// #Startup:ImGui Graphics Backend
app_imgui_init();
#if USING_SDL {
io := GetIO();
// Enable freetype rendering
io.Fonts.SetFontLoader(io.Fonts, FreeType.GetFontLoader());
// Setup imgui sdl3_gpu3 backend
success = ImplSDL3_InitForSDLGPU(platform.window);
ok_or_exit(success, "failed to InitForSDLGPU with error = %", consume_sdl_error());
init_info := ImplSDLGPU3_InitInfo.{
Device = gpu_device,
ColorTargetFormat = SDL_GetGPUSwapchainTextureFormat(gpu_device, platform.window),
MSAASamples = .SDL_GPU_SAMPLECOUNT_1,
SwapchainComposition = .SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
PresentMode = .SDL_GPU_PRESENTMODE_VSYNC,
};
success = ImplSDLGPU3_Init(*init_info);
ok_or_exit(success, "failed to init SDLGPU3 with error = %", consume_sdl_error());
}
#if USING_WINDOWS {
ImGui.ImplWin32_Init(hwnd);
ImGui.ImplDX11_Init(
cast,no_check(*ImGui.ID3D11Device) g_pd3dDevice,
cast,no_check(*ImGui.ID3D11DeviceContext) g_pd3dDeviceContext);
}
// #Startup:Font,Config,etc.
app_imgui_load_fonts();
app_imgui_config_setup();
app_apply_font_settings();
{ // #Startup:App
app_theme_startup(); // registry + settings.config + apply
if settings_write_back app_save_user_settings_file();
app_settings_watch_start();
app_ui_startup();
}
#if USING_WINDOWS || OS == .MACOS platform.main_context = *context;
// #MainLoop
while !app.should_exit_early {
defer reset_temporary_storage();
if !app_frame() break;
}
// ImGui otherwise writes the layout only on its periodic autosave, so recent changes were lost.
ini_filename := ImGui.GetIO().IniFilename;
if ini_filename ImGui.SaveIniSettingsToDisk(ini_filename);
// By the time the loop above exits, app_exit_shutdown_poll (called from app_frame) has already
// confirmed the background shutdown work finished.
if app.exit_code exit(app.exit_code); // e.g. a headless test run with failures
}
// Milliseconds since main() started, for the [startup] log lines.
startup_ms :: () -> float64 {
return to_float64_seconds(current_time_monotonic() - app.launch_time) * 1000.0;
}
// Returns false when the main loop should exit.
app_frame :: (in_live_resize := false) -> bool {
using platform; using ImGui;
app.frame_time = current_time_monotonic();
#if COMPILE_TESTS testing_cleanup();
platform_update_memory_usage_metrics();
app.frame_count += 1;
// #shared_memory_channel:bringToFront
if app.is_master && app.read_channel_fatal_errors < 2 {
message_success, fatal_error, message := Shared_Channel.reader_poll_for_message(*app.read_channel);
if fatal_error {
app.read_channel_fatal_errors += 1;
if app.read_channel_fatal_errors == 1 {
log_error("[IPC] The shared memory channel is corrupted; discarding its queue.");
Shared_Channel.reader_reset_queue(*app.read_channel);
} else {
log_error("[IPC] The shared memory channel is corrupted again; no longer polling it. Later launches will not bring this window forward.");
}
}
if message_success {
assert(message.user_read_bytes > 0);
log("[IPC] message received: message[%] %", message.user_read_bytes, []u8.{message.user_read_bytes, message.user_read_pointer});
command := cast(App_Shared_Memory_Tag)message.user_read_pointer[0];
if command == .Bring_To_Front {
app.restore_window_this_frame = true;
} else if command == .Show_Help {
app.restore_window_this_frame = true;
app.show_help_panel = true;
app.help_panel_focus = true;
} else if command == .Open_File && message.user_read_bytes > APP_SHARED_MEMORY_CHANNEL_HEADER_SIZE {
// Open in mcap, from a copy in view mode (app/open_file.jai): the path follows the header.
path := string.{message.user_read_bytes - APP_SHARED_MEMORY_CHANNEL_HEADER_SIZE, message.user_read_pointer + APP_SHARED_MEMORY_CHANNEL_HEADER_SIZE};
app.restore_window_this_frame = true;
if !app_exit_shutdown_started() open_file_request(path);
}
Shared_Channel.reader_done_with_message(*message);
}
}
// #frameStart
#if USING_SDL posix_exit_signal_check();
// Once the background shutdown thread owns the settings watch (exit_shutdown.jai), the main
// thread must leave it alone to avoid racing it.
shutting_down := app_exit_shutdown_started();
if !shutting_down {
app_settings_watch_frame();
}
app_check_key_combinations();
app_ui_frame_start();
// On macOS a live-resize frame runs from an event watch inside SDL_PollEvent, so it must not poll.
#if USING_SDL if !in_live_resize {
#if OS == .MACOS {
platform.polling_events = true;
platform.live_resize_frame_drawn = false;
}
ev: SDL_Event;
while(SDL_PollEvent(*ev)) {
// Send events to imgui through the backend, then do our processing
event_consumed := ImplSDL3_ProcessEvent(*ev);
if ev.type.(SDL_EventType) == {
case SDL_EventType.SDL_EVENT_WINDOW_MINIMIZED;
if !app.view_mode app.minimize_to_tray_this_frame = true;
case SDL_EventType.QUIT;
app.should_exit = true;
case SDL_EventType.WINDOW_CLOSE_REQUESTED;
if app.view_mode app.should_exit = true; // view mode has no tray
else app.minimize_to_tray_this_frame = true;
case;
}
}
SDL_PumpEvents();
SDL_UpdateTrays();
#if OS == .MACOS {
platform.polling_events = false;
// A live-resize frame drew during the poll and released the temporary storage it rebuilt the
// per-frame state (anything app_ui_frame_start builds) in, so this frame must not draw with it.
if platform.live_resize_frame_drawn return true;
}
} // #if USING_SDL
// A live-resize frame runs from WM_PAINT inside the pump, so it must neither pump nor swap devices.
#if USING_WINDOWS {
if !in_live_resize {
// Also checks registered global hotkeys! The flags it sets wait for the next frame if this one ends here.
if !Windows_Read_Messages() return true;
// Between the message pump and NewFrame is the only safe place for these; see #WarpFirstStartup.
d3d11_swap_to_hardware_if_ready();
d3d11_recover_lost_device_if_needed();
}
win32_apply_dpi_change(); // also in a live-resize frame: dragging across monitors happens there
}
#if USING_SDL sdl_apply_pending_app_icon(); // a frame after the restore below asked for it
if app.minimize_to_tray_this_frame {
app.minimize_to_tray_this_frame = false;
platform_minimize_to_tray();
}
if app.restore_window_this_frame {
app.restore_window_this_frame = false;
app.last_window_restore_time = current_time_monotonic();
platform_restore_window();
}
// Not from a live-resize frame: choosing the region runs a modal loop of its own.
if app.capture_region_this_frame && !in_live_resize {
app.capture_region_this_frame = false;
screen_capture_take();
app.capture_region_this_frame = false; // the hotkey pressed again while choosing
}
if app.record_region_this_frame && !in_live_resize {
app.record_region_this_frame = false;
screen_record_toggle();
app.record_region_this_frame = false;
app.capture_region_this_frame = false;
}
if app.should_exit {
app_exit_shutdown_start();
if app_exit_shutdown_poll() return false;
}
// Whichever way the window was hidden or brought back (tray click, hotkey, a second launch).
imgui_show_floating_windows(!app.minimized_to_tray);
if app.minimized_to_tray != app.was_minimized_to_tray {
app.was_minimized_to_tray = app.minimized_to_tray;
imgui_drop_stale_input();
// The minimize hotkey's 0.15 s guard, for every way back.
if !app.minimized_to_tray app.last_window_restore_time = current_time_monotonic();
}
#if USING_WINDOWS {
// While hidden in the tray, a lost device waits as any hidden frame does, not every 16 ms.
if g_device_lost || !g_pSwapChain {
if app.minimized_to_tray platform_wait_while_hidden(HIDDEN_WAIT_MS); else sleep_milliseconds(16);
return true;
}
if g_SwapChainOccluded && IDXGISwapChain_Present(g_pSwapChain, 0, DXGI_PRESENT_TEST) == DXGI_STATUS_OCCLUDED {
platform_wait_while_hidden(HIDDEN_WAIT_MS);
return true;
}
g_SwapChainOccluded = false;
D3D11_HandleWindowResize();
if d3d11_device_was_removed() || !g_mainRenderTargetView {
if app.minimized_to_tray platform_wait_while_hidden(HIDDEN_WAIT_MS); else sleep_milliseconds(16);
return true;
}
}
// :InputDisabledOnMinimize: no #UI block, so no input handling, runs while hidden.
if app.minimized_to_tray {
platform_wait_while_hidden(HIDDEN_WAIT_MS);
return true;
}
imgui_reset_layout_if_requested();
// #ImGui:NewFrame
#if USING_SDL {
ImplSDLGPU3_NewFrame();
ImplSDL3_NewFrame();
}
#if USING_WINDOWS {
ImGui.ImplDX11_NewFrame();
ImGui.ImplWin32_NewFrame();
}
ImGui.NewFrame();
{ // #UI Start
if !app.view_mode ImGui.DockSpaceOverViewport(); // view mode is one window filling the main one
app_debug_panel_ui();
app_frame_times_ui();
app_help_panel_ui();
app_style_editor_ui();
app_logger_panel_ui();
app_main_window_ui(); // app/app_ui.jai: start here
app_toast_ui();
app_exit_shutdown_overlay_ui(); // last, so it draws over everything else
} // #UI End
// #ImGui:Rendering
ImGui.Render();
swapchain_wait: Apollo_Time; // left out of the recorded work time
#if USING_SDL {
draw_data := GetDrawData();
is_minimized := (draw_data.DisplaySize.x <= 0 || draw_data.DisplaySize.y <= 0);
// Acquire a GPU command buffer and swapchain texture. With vsync these can block until the
// display frees an image (Metal's nextDrawable), which is the vsync wait, not work.
acquire_start := current_time_monotonic();
cmd_buffer := SDL_AcquireGPUCommandBuffer(gpu_device);
swapchain_texture: *SDL_GPUTexture;
success := SDL_AcquireGPUSwapchainTexture(cmd_buffer, platform.window, *swapchain_texture, null, null);
swapchain_wait = current_time_monotonic() - acquire_start;
if swapchain_texture && !is_minimized {
ImplSDLGPU3_PrepareDrawData(draw_data, cmd_buffer);
target_info := SDL_GPUColorTargetInfo.{
texture = swapchain_texture,
clear_color = app.ui_global_clear_color,
load_op = .CLEAR,
store_op = .STORE,
mip_level = 0,
layer_or_depth_plane = 0,
cycle = false,
};
render_pass := SDL_BeginGPURenderPass(cmd_buffer, *target_info, 1, null);
ImplSDLGPU3_RenderDrawData(draw_data, cmd_buffer, render_pass);
SDL_EndGPURenderPass(render_pass);
} else {
// Minimized to the Dock (no tray), there is no swapchain texture to wait on for vsync, so
// without this the loop would spin.
platform_wait_while_hidden(HIDDEN_WAIT_MS);
}
} // #if USING_SDL
// The frame is still ended, with UpdatePlatformWindows, but not drawn or presented.
draw_frame := true;
#if USING_WINDOWS {
draw_frame = !d3d11_device_was_removed();
if draw_frame {
ID3D11DeviceContext_OMSetRenderTargets(g_pd3dDeviceContext, 1, *g_mainRenderTargetView, null);
ID3D11DeviceContext_ClearRenderTargetView(g_pd3dDeviceContext, g_mainRenderTargetView, *app.ui_global_clear_color);
ImGui.ImplDX11_RenderDrawData(ImGui.GetDrawData());
}
}
io := ImGui.GetIO();
// #ImGui: Update and Render additional Platform Windows
if io.ConfigFlags_ & .ViewportsEnable {
// ImGui requires this every frame, but on a removed device the DX11 backend asserts creating a
// swap chain for a new floating window. None is made then: recovery recreates every window.
#if USING_WINDOWS {
platform_io := ImGui.GetPlatformIO();
renderer_create_window := platform_io.Renderer_CreateWindow;
if !draw_frame platform_io.Renderer_CreateWindow = null;
ImGui.UpdatePlatformWindows();
platform_io.Renderer_CreateWindow = renderer_create_window;
} else {
ImGui.UpdatePlatformWindows();
}
if draw_frame ImGui.RenderPlatformWindowsDefault();
}
work_seconds := to_float64_seconds(current_time_monotonic() - app.frame_time - swapchain_wait);
insert(*perf.frame_times, Frame_Timing.{work = xx work_seconds, delta = ImGui.GetIO().DeltaTime});
// #ImGui:Present and VSYNC
#if USING_SDL {
SDL_SubmitGPUCommandBuffer(cmd_buffer);
}
#if USING_WINDOWS {
if draw_frame DXGI_Handle_Vsync();
}
if perf.time_to_first_frame_ms == 0 {
perf.time_to_first_frame_ms = to_float64_seconds(app.frame_time - app.startup_time) * 1000.0;
}
if (perf.backend == .Vulkan || app.sleep_on_fast_frame) && (ImGui.GetIO().DeltaTime < QUICK_FRAME_DT) {
// Sleep for 1 ms after a quick frame to avoid pegging the core
sleep_milliseconds(1);
}
return true;
}
QUICK_FRAME_DT : float64 : 1.0 / 300.0;
// The IPC channel is only polled, so a hidden window still wakes this often to notice a
// Bring_To_Front from a second launch; window messages and SDL events end the wait sooner.
HIDDEN_WAIT_MS :: 50;
// POSIX shared memory names are machine-wide, so the name carries the user id; otherwise a
// second user's launch would find the first user's channel and exit.
app_shared_memory_channel_key :: () -> string {
#if OS == .MACOS {
// macOS caps the name at 31 characters (PSHMNAMLEN), which a long folder name plus a user id
// of six or more digits, as directory and mobile accounts have, would pass.
MAX_NAME_PREFIX :: 16;
prefix := APP_FOLDER_NAME;
if prefix.count > MAX_NAME_PREFIX prefix.count = MAX_NAME_PREFIX;
return sprint("%_%", prefix, POSIX.getuid());
} else #if USING_SDL {
return sprint("%_ipc_%", APP_FOLDER_NAME, POSIX.getuid());
} else {
return sprint("%_shared_memory_channel", APP_FOLDER_NAME);
}
}
#if USING_SDL {
// Per-user, so another user can neither plant a symlink at it nor hold it against this one.
app_instance_lock_path :: () -> string {
#if OS == .MACOS {
directory := to_string(SDL_getenv("TMPDIR")); // a per-user directory on macOS
} else {
directory := to_string(SDL_getenv("XDG_RUNTIME_DIR"));
if !directory {
// Unset under sudo, some ssh sessions, and non-systemd sessions.
home := to_string(SDL_getenv("HOME"));
if !home return "";
directory = tprint("%/.cache", home);
make_directory_if_it_does_not_exist(directory);
}
}
if !directory return "";
return tprint("%/%.lock", trim_right(directory, "/"), APP_FOLDER_NAME);
}
}
// Whether a running instance's channel can be connected to; nothing is sent.
app_running_instance_reachable :: (channel_key: string) -> bool {
connected, channel := Shared_Channel.writer_connect(channel_key, name_not_found_is_quiet=true);
if connected Shared_Channel.writer_disconnect(*channel);
return connected;
}
// Returns true once connected to a running instance, whether or not the message got through.
app_bring_running_instance_to_front :: (channel_key: string, attempts: int) -> bool {
return app_send_to_running_instance(channel_key, attempts, .Bring_To_Front);
}
// Sends tag, and payload after the header, to the running instance. True once connected to it,
// whether or not the message got through.
app_send_to_running_instance :: (channel_key: string, attempts: int, tag: App_Shared_Memory_Tag, payload := "") -> bool {
connected := false;
for 1..attempts {
connected, app.write_channel = Shared_Channel.writer_connect(channel_key, name_not_found_is_quiet=true);
if connected break;
if it < attempts sleep_milliseconds(50);
}
if !connected return false;
defer Shared_Channel.writer_disconnect(*app.write_channel);
// Reserving fails when the queue is full, e.g. the running instance has stopped polling.
message_success, message := Shared_Channel.writer_reserve_message(*app.write_channel, APP_SHARED_MEMORY_CHANNEL_HEADER_SIZE + payload.count);
if !message_success {
log_error("Another instance is running, but no space could be reserved in its message queue; % was not sent.", tag);
return true;
}
message.user_write_pointer[0] = cast(u8) tag;
message.user_write_pointer[1] = 0;
if payload.count memcpy(message.user_write_pointer + APP_SHARED_MEMORY_CHANNEL_HEADER_SIZE, payload.data, payload.count);
#if USING_WINDOWS AllowSetForegroundWindow(ASFW_ANY);
if !Shared_Channel.writer_commit_message(*app.write_channel, message) {
log_error("Another instance is running, but the % message could not be committed.", tag);
}
return true;
}
panic :: (format_string: string, args: .. Any, exit_code : s32 = 1) {
log_error(format_string, ..args);
exit(exit_code);
} @PrintLike