power_play/src/window/window_win32/window_win32.c

714 lines
22 KiB
C

WND_W32_SharedState WND_W32_shared_state = Zi;
////////////////////////////////////////////////////////////
//~ @hookimpl Bootstrap
void WND_Bootstrap(void)
{
WND_W32_SharedState *g = &WND_W32_shared_state;
//- Initialize btn table
{
ZeroFixedArray(g->vk_to_button);
for (u32 i = 'A', j = Button_A; i <= 'Z'; ++i, ++j)
{
g->vk_to_button[i] = (Button)j;
}
for (u32 i = '0', j = Button_0; i <= '9'; ++i, ++j)
{
g->vk_to_button[i] = (Button)j;
}
for (u32 i = VK_F1, j = Button_F1; i <= VK_F24; ++i, ++j)
{
g->vk_to_button[i] = (Button)j;
}
g->vk_to_button[VK_ESCAPE] = Button_Escape;
g->vk_to_button[VK_OEM_3] = Button_GraveAccent;
g->vk_to_button[VK_OEM_MINUS] = Button_Minus;
g->vk_to_button[VK_OEM_PLUS] = Button_Equal;
g->vk_to_button[VK_BACK] = Button_Backspace;
g->vk_to_button[VK_TAB] = Button_Tab;
g->vk_to_button[VK_SPACE] = Button_Space;
g->vk_to_button[VK_RETURN] = Button_Enter;
g->vk_to_button[VK_CONTROL] = Button_Ctrl;
g->vk_to_button[VK_SHIFT] = Button_Shift;
g->vk_to_button[VK_MENU] = Button_Alt;
g->vk_to_button[VK_UP] = Button_Up;
g->vk_to_button[VK_LEFT] = Button_Left;
g->vk_to_button[VK_DOWN] = Button_Down;
g->vk_to_button[VK_RIGHT] = Button_Right;
g->vk_to_button[VK_DELETE] = Button_Delete;
g->vk_to_button[VK_PRIOR] = Button_PageUp;
g->vk_to_button[VK_NEXT] = Button_PageDown;
g->vk_to_button[VK_HOME] = Button_Home;
g->vk_to_button[VK_END] = Button_End;
g->vk_to_button[VK_OEM_2] = Button_ForwardSlash;
g->vk_to_button[VK_OEM_PERIOD] = Button_Period;
g->vk_to_button[VK_OEM_COMMA] = Button_Comma;
g->vk_to_button[VK_OEM_7] = Button_Quote;
g->vk_to_button[VK_OEM_4] = Button_LeftBracket;
g->vk_to_button[VK_OEM_6] = Button_RightBracket;
g->vk_to_button[VK_INSERT] = Button_Insert;
g->vk_to_button[VK_OEM_1] = Button_Semicolon;
}
//- Initialize cursors
{
HCURSOR arrow = LoadCursor(0, IDC_ARROW);
for (u64 cursor_idx = 0; cursor_idx < countof(g->cursors); ++cursor_idx)
{
g->cursors[cursor_idx] = arrow;
}
g->cursors[WND_CursorKind_Text] = LoadCursor(0, IDC_IBEAM);
g->cursors[WND_CursorKind_No] = LoadCursor(0, IDC_NO);
g->cursors[WND_CursorKind_Hand] = LoadCursor(0, IDC_HAND);
g->cursors[WND_CursorKind_Move] = LoadCursor(0, IDC_SIZEALL);
g->cursors[WND_CursorKind_HorizontalResize] = LoadCursor(0, IDC_SIZEWE);
g->cursors[WND_CursorKind_VerticalResize] = LoadCursor(0, IDC_SIZENS);
g->cursors[WND_CursorKind_TlBrResize] = LoadCursor(0, IDC_SIZENWSE);
g->cursors[WND_CursorKind_TrBlResize] = LoadCursor(0, IDC_SIZENESW);
}
//- Create window class
{
HMODULE instance = GetModuleHandle(0);
/* Register the window class */
WNDCLASSEXW *wc = &g->window_class;
wc->cbSize = sizeof(WNDCLASSEX);
wc->lpszClassName = WND_W32_WindowClassName;
wc->hCursor = LoadCursor(0, IDC_ARROW);
wc->style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
// wc->hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc->lpfnWndProc = WND_W32_WindowProc;
wc->hInstance = instance;
/* Use first icon resource as window icon (same as explorer) */
wchar_t path[4096] = Zi;
GetModuleFileNameW(instance, path, countof(path));
ExtractIconExW(path, 0, &wc->hIcon, &wc->hIconSm, 1);
if (!RegisterClassExW(wc))
{
Panic(Lit("Failed to register window class"));
}
}
//- Register raw mouse input
{
RAWINPUTDEVICE rid = Zi;
rid.usUsagePage = 0x01; /* HID_USAGE_PAGE_GENERIC */
rid.usUsage = 0x02; /* HID_USAGE_GENERIC_MOUSE */
RegisterRawInputDevices(&rid, 1, sizeof(rid));
}
//- Dispatch message processor
DispatchWave(Lit("Win32 msg loop"), 1, WND_W32_ProcessMessagesForever, 0);
}
////////////////////////////////////////////////////////////
//~ Window helpers
WND_W32_Window *WND_W32_WindowFromHandle(WND_Handle handle)
{
return (WND_W32_Window *)handle.v;
}
////////////////////////////////////////////////////////////
//~ Initialization
/* Win32 limitation: Window must be initialized on same thread that processes events */
void WND_W32_ProcessMessagesForever(WaveLaneCtx *lane)
{
WND_W32_SharedState *g = &WND_W32_shared_state;
WND_W32_Window *window = &g->window;
window->w2u_events_arena = AcquireArena(Gibi(64));
//- Initialize hwnd
{
/*
* From martins (https://gist.github.com/mmozeiko/5e727f845db182d468a34d524508ad5f#file-win32_d3d11-c-L66-L70):
* WS_EX_NOREDIRECTIONBITMAP flag here is needed to fix ugly bug with Windows 10
* when window is resized and DXGI swap chain uses FLIP presentation model
* DO NOT use it if you choose to use non-FLIP presentation model
* read about the bug here: https://stackoverflow.com/q/63096226 and here: https://stackoverflow.com/q/53000291
*/
DWORD exstyle = WS_EX_APPWINDOW | WS_EX_NOREDIRECTIONBITMAP;
/* TODO: Check for hwnd success */
window->hwnd = CreateWindowExW(
exstyle,
g->window_class.lpszClassName,
L"",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
g->window_class.hInstance,
0
);
/* Dark mode */
BOOL dark_mode = 1;
DwmSetWindowAttribute(window->hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, (LPCVOID)&dark_mode, sizeof(dark_mode));
/* Set window as userdata */
/* FIXME: Necessary? */
SetWindowLongPtrW(window->hwnd, GWLP_USERDATA, (LONG_PTR)window);
}
//- Acquire swapchain
{
window->swapchain = G_AcquireSwapchain((u64)window->hwnd);
}
//- Begin processing messages
Atomic32Set(&window->is_ready, 1);
for (;;)
{
// SetFocus(window->hwnd);
MSG msg = Zi;
GetMessageW(&msg, 0, 0, 0);
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
/* Destroy window */
DestroyWindow(window->hwnd);
}
////////////////////////////////////////////////////////////
//~ Message processing
void WND_W32_PushEvent(WND_W32_Window *window, ControllerEvent event)
{
LockTicketMutex(&window->w2u_tm);
{
*PushStructNoZero(window->w2u_events_arena, ControllerEvent) = event;
}
UnlockTicketMutex(&window->w2u_tm);
}
LRESULT CALLBACK WND_W32_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
WND_W32_SharedState *g = &WND_W32_shared_state;
WND_W32_Window *window = &g->window;
LRESULT result = 0;
{
switch(msg)
{
//- Default
default:
{
result = DefWindowProcW(hwnd, msg, wparam, lparam);
} break;
//- Quit
case WM_QUIT:
case WM_CLOSE:
case WM_DESTROY:
{
WND_W32_PushEvent(window, (ControllerEvent) { .kind = ControllerEventKind_Quit });
} break;
//- Cursor kind
case WM_SETCURSOR:
{
/* FIXME */
HCURSOR new_cursor = (HCURSOR)Atomic64Fetch(&window->new_cursor);
if (new_cursor != window->active_cursor)
{
SetCursor(new_cursor);
window->active_cursor = new_cursor;
result = 1;
}
} break;
//- Keyboard button
case WM_SYSKEYUP:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_KEYDOWN:
{
WORD vk_code = LOWORD(wparam);
ControllerEvent event = Zi;
if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN)
{
event.kind = ControllerEventKind_ButtonDown;
event.is_repeat = (lparam & 0x40000000) != 0;
}
else if (msg == WM_KEYUP || msg == WM_SYSKEYUP)
{
event.kind = ControllerEventKind_ButtonUp;
}
if (vk_code < countof(g->vk_to_button))
{
event.button = g->vk_to_button[vk_code];
}
WND_W32_PushEvent(window, event);
if (msg == WM_SYSKEYUP || msg == WM_SYSKEYDOWN)
{
result = DefWindowProcW(hwnd, msg, wparam, lparam);
}
} break;
//- Text
case WM_SYSCHAR:
case WM_CHAR:
{
u32 codepoint = 0;
{
u16 utf16_char = (u32)wparam;
if (IsUtf16HighSurrogate(utf16_char))
{
window->previous_utf16_high_surrogate = utf16_char;
}
else if (IsUtf16LowSurrogate(utf16_char))
{
u16 high = window->previous_utf16_high_surrogate;
u16 low = utf16_char;
if (high)
{
u16 utf16_pair_bytes[2] = { high, low };
Utf16DecodeResult decoded = DecodeUtf16((String16) { .len = countof(utf16_pair_bytes), .text = utf16_pair_bytes });
if (decoded.advance16 == 2 && decoded.codepoint < U32Max)
{
codepoint = decoded.codepoint;
}
}
window->previous_utf16_high_surrogate = 0;
}
else
{
window->previous_utf16_high_surrogate = 0;
codepoint = utf16_char;
}
if (codepoint != 0)
{
if (codepoint == '\r')
{
codepoint = '\n'; /* Just treat all \r as newline */
}
}
}
if ((codepoint >= 32 && codepoint != 127) || codepoint == '\t' || codepoint == '\n')
{
ControllerEvent event = Zi;
event.kind = ControllerEventKind_Text;
event.text_codepoint = codepoint;
WND_W32_PushEvent(window, event);
}
} break;
//- Mouse buttons
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_XBUTTONUP:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_XBUTTONDOWN:
{
ControllerEvent event = Zi;
b32 is_down = msg == WM_LBUTTONDOWN ||
msg == WM_MBUTTONDOWN ||
msg == WM_RBUTTONDOWN ||
msg == WM_XBUTTONDOWN;
if (is_down)
{
event.kind = ControllerEventKind_ButtonDown;
SetCapture(hwnd);
}
else
{
event.kind = ControllerEventKind_ButtonUp;
ReleaseCapture();
}
switch (msg)
{
case WM_LBUTTONUP: case WM_LBUTTONDOWN: event.button = Button_M1; break;
case WM_RBUTTONUP: case WM_RBUTTONDOWN: event.button = Button_M2; break;
case WM_MBUTTONUP: case WM_MBUTTONDOWN: event.button = Button_M3; break;
case WM_XBUTTONUP: case WM_XBUTTONDOWN:
{
u32 wparam_xbutton = GET_XBUTTON_WPARAM(wparam);
if (wparam_xbutton == XBUTTON1)
{
event.button = Button_M4;
}
else if (wparam_xbutton == XBUTTON2)
{
event.button = Button_M5;
}
} break;
}
if (event.button)
{
WND_W32_PushEvent(window, event);
}
} break;
//- Mouse wheel
case WM_MOUSEWHEEL:
{
int delta = GET_WHEEL_DELTA_WPARAM(wparam);
i32 dir = delta >= 0 ? 1 : -1;
Button btn = dir >= 0 ? Button_MWheelUp : Button_MWheelDown;
for (i32 i = 0; i < (dir * delta); i += WHEEL_DELTA)
{
/* Send a button down & button up event simultaneously */
WND_W32_PushEvent(window, (ControllerEvent) { .kind = ControllerEventKind_ButtonDown, .button = btn });
WND_W32_PushEvent(window, (ControllerEvent) { .kind = ControllerEventKind_ButtonUp, .button = btn });
}
} break;
//- Cursor move
case WM_MOUSEMOVE:
{
i32 x = GET_X_LPARAM(lparam);
i32 y = GET_Y_LPARAM(lparam);
ControllerEvent event = Zi;
event.kind = ControllerEventKind_CursorMove;
event.cursor_pos = VEC2I32(x, y);
WND_W32_PushEvent(window, event);
} break;
//- Raw mouse move
case WM_INPUT:
{
TempArena scratch = BeginScratchNoConflict();
{
/* Read raw input buffer */
UINT buff_size;
GetRawInputData((HRAWINPUT)lparam, RID_INPUT, 0, &buff_size, sizeof(RAWINPUTHEADER));
u8 *buff = PushStructs(scratch.arena, u8, buff_size);
if (GetRawInputData((HRAWINPUT)lparam, RID_INPUT, buff, &buff_size, sizeof(RAWINPUTHEADER)) != buff_size)
{
LogErrorF("GetRawInputData did not return correct size");
break;
}
RAWINPUT raw = Zi;
CopyBytes(&raw, buff, sizeof(RAWINPUT));
if (raw.header.dwType == RIM_TYPEMOUSE)
{
i32 x = raw.data.mouse.lLastX;
i32 y = raw.data.mouse.lLastY;
ControllerEvent event = Zi;
event.kind = ControllerEventKind_MouseMove;
event.mouse_delta = VEC2I32(x, y);
WND_W32_PushEvent(window, event);
}
}
EndScratch(scratch);
} break;
}
}
return result;
}
////////////////////////////////////////////////////////////
//~ @hookimpl Command
void WND_PushCmd_(WND_Frame frame, WND_Cmd desc)
{
WND_W32_Window *window = WND_W32_WindowFromHandle(frame.window);
WND_W32_CmdNode *n = PushStruct(window->frame_arena, WND_W32_CmdNode);
n->cmd = desc;
SllQueuePush(window->first_cmd, window->last_cmd, n);
if (desc.kind == WND_CmdKind_Restore)
{
n->cmd.restore = PushString(window->frame_arena, desc.restore);
}
}
////////////////////////////////////////////////////////////
//~ @hookimpl Frame
WND_Frame WND_BeginFrame(G_Format backbuffer_format, WND_BackbufferSizeMode backbuffer_size_mode)
{
WND_W32_SharedState *g = &WND_W32_shared_state;
WND_W32_Window *window = &g->window;
WND_Frame result = Zi;
while (!Atomic32Fetch(&window->is_ready))
{
_mm_pause();
}
HWND hwnd = window->hwnd;
result.window.v = (u64)window;
/* Grab monitor info */
RECT monitor_rect = Zi;
{
MONITORINFO monitor_info = { .cbSize = sizeof(monitor_info) };
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info);
monitor_rect = monitor_info.rcMonitor;
}
result.monitor_size = VEC2I32(monitor_rect.right - monitor_rect.left, monitor_rect.bottom - monitor_rect.top);
result.monitor_size.x = MaxI32(result.monitor_size.x, 1);
result.monitor_size.y = MaxI32(result.monitor_size.y, 1);
/* Client rect */
RECT client_rect = Zi;
GetClientRect(hwnd, (LPRECT)&client_rect);
/* Screen rect */
RECT screen_rect = client_rect;
ClientToScreen(hwnd, (LPPOINT)&screen_rect.left);
ClientToScreen(hwnd, (LPPOINT)&screen_rect.right);
result.draw_size = VEC2I32(screen_rect.right - screen_rect.left, screen_rect.bottom - screen_rect.top);
result.draw_size.x = MaxI32(result.draw_size.x, 1);
result.draw_size.y = MaxI32(result.draw_size.y, 1);
/* Prepare backbuffer */
Vec2I32 backbuffer_size = Zi;
if (backbuffer_size_mode == WND_BackbufferSizeMode_MatchWindow)
{
backbuffer_size = result.draw_size;
}
else if (backbuffer_size_mode == WND_BackbufferSizeMode_MatchMonitor)
{
backbuffer_size = result.monitor_size;
}
result.backbuffer = G_PrepareBackbuffer(window->swapchain, backbuffer_format, backbuffer_size);
/* Reset per-frame data */
if (!window->frame_arena)
{
window->frame_arena = AcquireArena(Gibi(64));
}
ResetArena(window->frame_arena);
window->first_cmd = 0;
window->last_cmd = 0;
/* Pop user input */
{
LockTicketMutex(&window->w2u_tm);
{
ControllerEvent *src = ArenaFirst(window->w2u_events_arena, ControllerEvent);
result.controller_events.count = ArenaCount(window->w2u_events_arena, ControllerEvent);
result.controller_events.events = PushStructsNoZero(window->frame_arena, ControllerEvent, result.controller_events.count);
CopyStructs(result.controller_events.events, src, result.controller_events.count);
ResetArena(window->w2u_events_arena);
}
UnlockTicketMutex(&window->w2u_tm);
}
/* Minimized / maximized / fullscreen */
DWORD style = (DWORD)GetWindowLongPtr(hwnd, GWL_STYLE);
DWORD ex_style = (DWORD)GetWindowLongPtr(hwnd, GWL_EXSTYLE);
WINDOWPLACEMENT placement = { .length = sizeof(placement) };
GetWindowPlacement(hwnd, &placement);
{
if (placement.showCmd == SW_MAXIMIZE)
{
result.maximized = 1;
}
if (placement.showCmd == SW_MINIMIZE)
{
result.minimized = 1;
}
if (
screen_rect.left == monitor_rect.left &&
screen_rect.top == monitor_rect.top &&
screen_rect.right == monitor_rect.right &&
screen_rect.bottom == monitor_rect.bottom
)
{
result.fullscreen = 1;
}
}
result.forced_top = window->is_forced_top;
result.fullscreen = window->is_fullscreen;
result.has_focus = GetForegroundWindow() == hwnd;
/* Generate restore data */
{
WND_W32_RestorableData restore = Zi;
{
restore.magic = WND_W32_RestoreMagic;
restore.placement = placement;
restore.style = style;
restore.ex_style = ex_style;
restore.is_forced_top = window->is_forced_top;
restore.is_fullscreen = window->is_fullscreen;
restore.fullscreen_restore_rect = window->fullscreen_restore_rect;
restore.has_focus = result.has_focus;
if (IsWindowArranged(hwnd))
{
restore.is_snapped = 1;
restore.snapped_screen_rect = screen_rect;
}
}
result.restore = PushString(window->frame_arena, StringFromStruct(&restore));
}
return result;
}
void WND_EndFrame(WND_Frame frame, i32 vsync)
{
TempArena scratch = BeginScratchNoConflict();
WND_W32_SharedState *g = &WND_W32_shared_state;
WND_W32_Window *window = WND_W32_WindowFromHandle(frame.window);
HWND hwnd = window->hwnd;
/* Process cmds */
b32 was_restored = 0;
WND_CursorKind new_cursor = (WND_CursorKind)Atomic64Fetch(&window->new_cursor);
for (WND_W32_CmdNode *n = window->first_cmd; n; n = n->next)
{
WND_Cmd cmd = n->cmd;
switch(cmd.kind)
{
default: break;
//- Minimize
case WND_CmdKind_SetMinimized:
{
ShowWindow(hwnd, SW_MINIMIZE);
} break;
//- Maximize
case WND_CmdKind_SetMaximized:
{
ShowWindow(hwnd, SW_MAXIMIZE);
} break;
//- Fullscreen
case WND_CmdKind_SetFullscreen:
{
if (cmd.v != window->is_fullscreen)
{
DWORD old_style = (DWORD)GetWindowLongPtr(hwnd, GWL_STYLE);
RECT old_rect = Zi;
{
GetClientRect(hwnd, (LPRECT)&old_rect);
ClientToScreen(hwnd, (LPPOINT)&old_rect.left);
ClientToScreen(hwnd, (LPPOINT)&old_rect.right);
AdjustWindowRect(&old_rect, old_style, 0);
}
RECT new_rect = Zi;
HWND new_hwnd = 0;
DWORD new_style = old_style;
if (cmd.v)
{
/* Enter fullscreen */
{
MONITORINFO monitor_info = { .cbSize = sizeof(monitor_info) };
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info);
new_rect = monitor_info.rcMonitor;
}
new_style &= ~WS_OVERLAPPEDWINDOW;
new_style |= WS_POPUP;
window->fullscreen_restore_rect = old_rect;
new_hwnd = HWND_TOP;
}
else
{
/* Exit fullscreen */
new_rect = window->fullscreen_restore_rect;
new_style &= ~WS_POPUP;
new_style |= WS_OVERLAPPEDWINDOW;
new_hwnd = window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST;
}
window->is_fullscreen = cmd.v;
SetWindowLongPtr(hwnd, GWL_STYLE, new_style);
SetWindowPos(hwnd, new_hwnd, new_rect.left, new_rect.top, new_rect.right - new_rect.left, new_rect.bottom - new_rect.top, 0);
}
} break;
//- Force top
case WND_CmdKind_SetForcedTop:
{
window->is_forced_top = cmd.v;
SetWindowPos(hwnd, window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
} break;
//- Cursor
case WND_CmdKind_SetCursor:
{
new_cursor = cmd.cursor;
} break;
//- Restore
case WND_CmdKind_Restore:
{
/* FIXME: Cap bounds */
String restore_str = cmd.restore;
if (restore_str.len == sizeof(WND_W32_RestorableData))
{
WND_W32_RestorableData *restore = PushStructNoZero(scratch.arena, WND_W32_RestorableData);
CopyBytes(restore, restore_str.text, restore_str.len);
if (restore->magic == WND_W32_RestoreMagic)
{
WINDOWPLACEMENT placement = restore->placement;
was_restored = 1;
SetWindowPlacement(hwnd, &placement);
SetWindowLongPtr(hwnd, GWL_STYLE, restore->style);
SetWindowLongPtr(hwnd, GWL_EXSTYLE, restore->ex_style);
if (restore->is_fullscreen)
{
MONITORINFO monitor_info = { .cbSize = sizeof(monitor_info) };
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &monitor_info);
RECT rect = monitor_info.rcMonitor;
window->is_fullscreen = 1;
window->fullscreen_restore_rect = restore->fullscreen_restore_rect;
SetWindowPos(hwnd, HWND_TOP, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0);
}
if (restore->is_forced_top)
{
window->is_forced_top = 1;
SetWindowPos(hwnd, window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
if (restore->is_snapped)
{
RECT rect = restore->snapped_screen_rect;
HWND pos_hwnd = window->is_forced_top ? HWND_TOPMOST : HWND_NOTOPMOST;
SetWindowPos(hwnd, pos_hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0);
}
if (restore->has_focus)
{
ShowWindow(hwnd, SW_SHOW);
SetForegroundWindow(hwnd);
BringWindowToTop(hwnd);
}
}
}
} break;
}
}
/* Bring window to front on first frame */
if (!was_restored && window->frame_gen == 0)
{
ShowWindow(hwnd, SW_SHOW);
SetForegroundWindow(hwnd);
BringWindowToTop(hwnd);
}
/* Set cursor */
Atomic64Set(&window->new_cursor, (u64)g->cursors[new_cursor]);
/* Commit backbuffer */
G_CommitBackbuffer(frame.backbuffer, vsync);
++window->frame_gen;
EndScratch(scratch);
}