/* * Overreact - Mr. 4th Dimention * Allen Webster * 03.21.2015 (mm.dd.yyyy) * * Platform Layer: Win32. */ // TOP // TODO(allen): memcpy sound samples for performance // TODO(allen): migrate as much as possible to c standard lib #include "jam_meta.h" #include #include #include #define SOFTWARE 0 #define OPENGL 1 #define DISABLE_CONTROLLER 1 #define RENDER_MODE OPENGL #if RENDER_MODE == OPENGL #include #endif #include "jam_system.h" #include "jam_rendering_system.h" #include "jam_rendering.h" #include "jam.h" #include "jam_math.cpp" #include "jam_graphics.h" #include "jam_graphics.cpp" #include "jam_audio.h" #include "jam_audio.cpp" #include "gjCStyle.h" #include "jam_game.h" #include "jam.cpp" #include #if !DISABLE_CONTROLLER #include #endif #include #define WINERROR(msg) #include "jam_rendering_win32.cpp" internal SIG_SYS_LOAD_FILE(system_load_file){ File result = {}; HANDLE winfile = CreateFileA(filename, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (winfile){ LARGE_INTEGER size; // TODO(allen): GetFileSizeEx is incompatible with windows store. if (GetFileSizeEx(winfile, &size)){ result.size = size.QuadPart; result.data = VirtualAlloc(0, (i32)result.size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); u32 unread = (u32)result.size; while (unread != 0){ DWORD read_size = Min(unread, max_u32); DWORD true_read_size; u8 *write_pos = (u8*)result.data; if (ReadFile(winfile, write_pos, read_size, &true_read_size, 0)){ if (unread >= true_read_size){ unread -= true_read_size; write_pos += true_read_size; } else{ break; } } else{ break; } } if (unread != 0){ system_free_file(result); result = {}; } } CloseHandle(winfile); } return result; } internal SIG_SYS_SAVE_FILE(system_save_file){ HANDLE winfile = CreateFileA(filename, GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (winfile){ u32 unwritten = (u32)file.size; while (unwritten != 0){ DWORD write_size = Min(unwritten, max_u32); DWORD true_write_size; u8 *src_pos = (u8*)file.data; if (ReadFile(winfile, src_pos, write_size, &true_write_size, 0)){ if (unwritten >= true_write_size){ unwritten -= true_write_size; src_pos += true_write_size; } else{ break; } } else{ break; } } } } internal SIG_SYS_FREE_FILE(system_free_file){ VirtualFree(file.data, 0, MEM_RELEASE); } #if !DISABLE_CONTROLLER #define SIG_Input_Get_State(name) DWORD(name)(DWORD user_index, XINPUT_STATE *state) #define SIG_Input_Set_State(name) DWORD(name)(DWORD user_index, XINPUT_VIBRATION *vibration) typedef SIG_Input_Get_State(Input_Get_State); typedef SIG_Input_Set_State(Input_Set_State); struct Win32ControllerFunctions{ bool32 is_valid; Input_Get_State *get_state; Input_Set_State *set_state; }; #endif #define SIG_Sound_Create(name) HRESULT(name)(LPGUID guid, LPDIRECTSOUND *ds, LPUNKNOWN unk); typedef SIG_Sound_Create(Sound_Create); struct Win32SoundFunctions{ Sound_Create *create; }; struct Win32Vars{ Game_Memory memory; HWND winhandle; Win32RenderingVars render_vars; i32 min_width, min_height; i32 min_game_width, min_game_height; i32 pref_game_width, pref_game_height; bool32 is_fullscreen; bool32 has_prev_winrect; RECT prev_winrect; LPDIRECTSOUNDBUFFER sound_buffer; i32 samples_per_second; i32 bytes_per_sample; i32 sound_buffer_size; u32 sound_sample_index; i16* samples; i32 sound_safety_margin; i16 mx, my; i64 msecond_per_frame; real32 update_hz; bool32 keep_playing; bool32 first; Game_Input keyboard_input, controller_input; #if 0 Win32ControllerFunctions xin; #endif bool32 toggle_full_screen; char btn1, btn2; }; globalvar Win32Vars win32; internal SIS_SYS_SET_KEYBIND(system_set_keybind){ Assert(key >= 'A' && key <= 'Z'); if (btn_id == 1){ win32.btn1 = key; } else if (btn_id == 2){ win32.btn2 = key; } } #if 0 internal void Win32InputSetup(){ HMODULE xinput = 0; xinput = LoadLibrary("Xinput1_4.dll"); if (!xinput){ xinput = LoadLibrary("Xinput9_1_0.dll"); } if (!xinput){ xinput = LoadLibrary("Xinput1_3.dll"); } if (xinput){ win32.xin.get_state = (Input_Get_State*)GetProcAddress(xinput, "XInputGetState"); win32.xin.set_state = (Input_Set_State*)GetProcAddress(xinput, "XInputSetState"); if (!win32.xin.get_state || !win32.xin.set_state){ win32.xin = {}; } else{ win32.xin.is_valid = 1; } } } struct Win32ControllerData{ real32 lstick_x, lstick_y; real32 rstick_x, rstick_y; bool8 plugged_in; bool8 up, down, left, right; bool8 start, back; bool8 left_thumb, right_thumb; bool8 left_shoulder, right_shoulder; bool8 a, b, x, y; bool8 ltrigger, rtrigger; }; inline internal void Win32RealStick(real32 *stick, i16 input, i16 deadzone){ if (input > deadzone){ *stick = (input - deadzone) / (32767.f - deadzone); } else if (input < -deadzone){ *stick = (input + deadzone) / (32768.f - deadzone); } else{ *stick = 0; } } internal void Win32ControllerGet(Win32ControllerData *data, i32 index){ XINPUT_STATE state; DWORD success = win32.xin.get_state(index, &state); data->plugged_in = (success == ERROR_SUCCESS); if (success == ERROR_SUCCESS){ data->up = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_UP); data->down = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_DOWN); data->left = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_LEFT); data->right = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_DPAD_RIGHT); data->start = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_START); data->back = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_BACK); data->left_thumb = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_LEFT_THUMB); data->right_thumb = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_RIGHT_THUMB); data->left_shoulder = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_LEFT_SHOULDER); data->right_shoulder = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_RIGHT_SHOULDER); data->a = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_A); data->b = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_B); data->x = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_X); data->y = CheckFlag(state.Gamepad.wButtons, XINPUT_GAMEPAD_Y); if (state.Gamepad.bLeftTrigger >= 224){ data->ltrigger = 1; } else{ data->ltrigger = 0; } if (state.Gamepad.bRightTrigger >= 224){ data->rtrigger = 1; } else{ data->rtrigger = 0; } Win32RealStick(&data->lstick_x, state.Gamepad.sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); Win32RealStick(&data->lstick_y, state.Gamepad.sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); Win32RealStick(&data->rstick_x, state.Gamepad.sThumbRX, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); Win32RealStick(&data->rstick_y, state.Gamepad.sThumbRY, XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); } } internal bool32 Win32ControllerRumble(i32 index, real32 left, real32 right){ XINPUT_VIBRATION vals; vals.wLeftMotorSpeed = (u16)(left*max_u16); vals.wRightMotorSpeed = (u16)(right*max_u16); DWORD success = win32.xin.set_state(index, &vals); return (success == ERROR_SUCCESS); } #endif internal Win32SoundFunctions Win32SoundSetup(i32 samples_per_second, i32 buffer_size){ Win32SoundFunctions funcs = {}; HMODULE dsound = LoadLibrary("dsound.dll"); if (dsound){ funcs.create = (Sound_Create*)GetProcAddress(dsound, "DirectSoundCreate"); if (funcs.create){ LPDIRECTSOUND dsound; if (SUCCEEDED(funcs.create(0, &dsound, 0))){ WAVEFORMATEX wave_format = {}; wave_format.wFormatTag = WAVE_FORMAT_PCM; wave_format.nChannels = 2; wave_format.nSamplesPerSec = samples_per_second; wave_format.wBitsPerSample = 16; wave_format.nBlockAlign = (wave_format.nChannels * wave_format.wBitsPerSample) / 8; wave_format.nAvgBytesPerSec = wave_format.nSamplesPerSec*wave_format.nBlockAlign; wave_format.cbSize = 0; if (SUCCEEDED(dsound->SetCooperativeLevel(win32.winhandle, DSSCL_PRIORITY))){ DSBUFFERDESC buffer_description = {}; buffer_description.dwSize = sizeof(buffer_description); buffer_description.dwFlags = DSBCAPS_PRIMARYBUFFER; LPDIRECTSOUNDBUFFER buffer; if (SUCCEEDED(dsound->CreateSoundBuffer(&buffer_description, &buffer, 0))){ if (buffer->SetFormat(&wave_format)){ // NOTE(allen): SUCCESS - set primary buffer format } else{ // NOTE(allen): FAIL - set primary buffer format } } else{ // NOTE(allen): FAIL - create primary buffer } } else{ // NOTE(allen): FAIL - set cooperative level } DSBUFFERDESC buffer_description = {}; buffer_description.dwSize = sizeof(buffer_description); buffer_description.dwFlags = DSBCAPS_GETCURRENTPOSITION2; buffer_description.dwBufferBytes = buffer_size; buffer_description.lpwfxFormat = &wave_format; if (SUCCEEDED(dsound->CreateSoundBuffer(&buffer_description, &win32.sound_buffer, 0))){ // NOTE(allen): SUCCESS - create secondary buffer } else{ // NOTE(allen): FAIL - create secondary buffer } } else{ // NOTE(allen): FAIL - create direct sound interface } } else{ // NOTE(allen): FAIL - load direct sound creator function } } else{ // NOTE(allen): FAIL - load direct sound library } return funcs; } internal void Win32ClearSound(){ VOID *chunk1, *chunk2; DWORD size1, size2; if (SUCCEEDED(win32.sound_buffer->Lock(0, win32.sound_buffer_size, &chunk1, &size1, &chunk2, &size2, 0))){ u8 *dest = (u8*)chunk1; if (dest){ for (DWORD i = 0; i < size1; ++i){ *dest = 0; ++dest; } } dest = (u8*)chunk2; if (dest){ for (DWORD i = 0; i < size1; ++i){ *dest = 0; ++dest; } } win32.sound_buffer->Unlock(chunk1, size1, chunk2, size2); } } internal void Win32FillSound(DWORD write_start, DWORD write_size, i16 *source){ VOID *chunk1, *chunk2; DWORD size1, size2; if (SUCCEEDED(win32.sound_buffer->Lock(write_start, write_size, &chunk1, &size1, &chunk2, &size2, 0))){ DWORD samples = size1/win32.bytes_per_sample; i16 *dest = (i16*)chunk1; if (dest){ for (DWORD i = 0; i < samples; ++i){ *dest++ = *source++; *dest++ = *source++; ++win32.sound_sample_index; } } samples = size2/win32.bytes_per_sample; dest = (i16*)chunk2; if (dest){ for (DWORD i = 0; i < samples; ++i){ *dest++ = *source++; *dest++ = *source++; ++win32.sound_sample_index; } } win32.sound_buffer->Unlock(chunk1, size1, chunk2, size2); } } inline internal void Win32UpdateKey(bool32 press, bool32 release, bool8 *key){ if (press){ *key = 1; } if (release){ *key = 0; } } internal LRESULT Win32Proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ LRESULT result = {}; switch (uMsg){ case WM_KEYDOWN: case WM_KEYUP: case WM_SYSKEYDOWN: case WM_SYSKEYUP: { bool32 prev, current; prev = ((lParam & Bit_30)?(1):(0)); current = ((lParam & Bit_31)?(0):(1)); bool32 press = current && !prev; bool32 release = prev && !current; switch (wParam){ case VK_UP: { Win32UpdateKey(press, release, &win32.keyboard_input.digital.up); }break; case VK_DOWN: { Win32UpdateKey(press, release, &win32.keyboard_input.digital.down); }break; case VK_LEFT: { Win32UpdateKey(press, release, &win32.keyboard_input.digital.left); }break; case VK_RIGHT: { Win32UpdateKey(press, release, &win32.keyboard_input.digital.right); }break; case VK_SPACE: case VK_RETURN: { Win32UpdateKey(press, release, &win32.keyboard_input.button[0]); if (uMsg == WM_KEYDOWN){ win32.keyboard_input.key_input = 1; win32.keyboard_input.key_code = 1; } }break; case VK_BACK: { Win32UpdateKey(press, release, &win32.keyboard_input.back); if (uMsg == WM_KEYDOWN){ win32.keyboard_input.key_input = 1; win32.keyboard_input.key_code = 0; } }break; case VK_ESCAPE: { Win32UpdateKey(press, release, &win32.keyboard_input.pause); }break; case VK_F2: { Win32UpdateKey(press, release, &win32.keyboard_input.toggle_edit_mode); }break; case VK_SHIFT: break; case VK_CONTROL: { #if 0 if (press){ win32.toggle_full_screen = 1; } #endif }break; case VK_MENU: { win32.first = 1; }break; /*ALT*/ default: { if ((char)wParam == win32.btn1){ Win32UpdateKey(press, release, &win32.keyboard_input.button[1]); } if ((char)wParam == win32.btn2){ Win32UpdateKey(press, release, &win32.keyboard_input.button[2]); } if (uMsg == WM_KEYDOWN){ if ((wParam >= '0' && wParam <= '9') || (wParam >= 'A' && wParam <= 'Z')){ win32.keyboard_input.key_input = 1; win32.keyboard_input.key_code = (char)wParam; } } }break; } }break; case WM_LBUTTONDOWN: { win32.mx = LOWORD(lParam); win32.my = HIWORD(lParam); }break; case WM_LBUTTONUP: { win32.mx = LOWORD(lParam); win32.my = HIWORD(lParam); }break; case WM_RBUTTONDOWN: { win32.mx = LOWORD(lParam); win32.my = HIWORD(lParam); }break; case WM_RBUTTONUP: { win32.mx = LOWORD(lParam); win32.my = HIWORD(lParam); }break; case WM_MOUSEMOVE: { win32.mx = LOWORD(lParam); win32.my = HIWORD(lParam); }break; case WM_SIZE: { i32 w, h; w = LOWORD(lParam); h = HIWORD(lParam); render_set_screen_size(&win32.render_vars, w, h, win32.pref_game_width, win32.pref_game_height); }break; case WM_GETMINMAXINFO: { MINMAXINFO* mmi = (MINMAXINFO*)lParam; mmi->ptMinTrackSize.x = win32.min_width; mmi->ptMinTrackSize.y = win32.min_height; return 0; } case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); AllowLocal(hdc); //win32.render_vars.hdc = hdc; render_redraw_screen(&win32.render_vars); EndPaint(hwnd, &ps); }break; case WM_CLOSE: case WM_DESTROY: { win32.keep_playing = 0; }break; case WM_NCLBUTTONDOWN: { DWORD status; win32.sound_buffer->GetStatus(&status); if(status & DSBSTATUS_PLAYING){ win32.sound_buffer->Stop(); } result = DefWindowProc(hwnd, uMsg, wParam, lParam); }break; case WM_ENTERSIZEMOVE: { DWORD status; win32.sound_buffer->GetStatus(&status); if(status & DSBSTATUS_PLAYING){ win32.sound_buffer->Stop(); } }break; case WM_EXITSIZEMOVE: { DWORD status; win32.sound_buffer->GetStatus(&status); if(!(status & DSBSTATUS_PLAYING)){ win32.sound_buffer->Play(0, 0, DSBPLAY_LOOPING); } }break; default: { result = DefWindowProc(hwnd, uMsg, wParam, lParam); }break; } return result; } internal i64 Win32GetTime(){ i64 result = 0; LARGE_INTEGER time; if (QueryPerformanceCounter(&time)){ result = (i64)time.QuadPart; } return result; } internal bool32 Win32GoFullscreen(){ HMONITOR monitor = MonitorFromWindow(win32.winhandle, MONITOR_DEFAULTTOPRIMARY); if (!monitor){ WINERROR("Failed to get monitor handle"); return 0; } MONITORINFO monitor_info; monitor_info.cbSize = sizeof(monitor_info); if (!GetMonitorInfo(monitor, &monitor_info)){ WINERROR("Failed to get monitor information"); return 0; } if (!GetWindowRect(win32.winhandle, &win32.prev_winrect)){ WINERROR("Failed to save previous window size"); } else{ win32.has_prev_winrect = 1; } SetWindowLongPtr(win32.winhandle, GWL_STYLE, WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE); MoveWindow(win32.winhandle, monitor_info.rcMonitor.left, monitor_info.rcMonitor.top, monitor_info.rcMonitor.right - monitor_info.rcMonitor.left, monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top, TRUE); win32.is_fullscreen = 1; return 1; } internal bool32 Win32GoWindowed(){ SetWindowLongPtr(win32.winhandle, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE); if (win32.has_prev_winrect){ MoveWindow(win32.winhandle, win32.prev_winrect.left, win32.prev_winrect.top, win32.prev_winrect.right - win32.prev_winrect.left, win32.prev_winrect.bottom - win32.prev_winrect.top, TRUE); } else{ RECT winrect = {}; winrect.right = win32.min_game_width; winrect.bottom = win32.min_game_height; if (!AdjustWindowRect(&winrect, WS_OVERLAPPEDWINDOW, 0)){ return 0; } i32 win_x, win_y; win_x = 0; win_y = 0; // TODO(allen): This is now duplicated... so fix that? HMONITOR monitor = MonitorFromWindow(win32.winhandle, MONITOR_DEFAULTTOPRIMARY); if (!monitor){ WINERROR("Failed to get monitor handle"); } else{ MONITORINFO monitor_info; monitor_info.cbSize = sizeof(monitor_info); if (!GetMonitorInfo(monitor, &monitor_info)){ WINERROR("Failed to get monitor information"); } else{ // TODO: This will be easier when the get monitor info stuff // is pulled into it's own function so that it is only a matter // of seeing the success or failure of that one call. win_x = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left - (winrect.right - winrect.left); win_y = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top - (winrect.bottom - winrect.top); win_x /= 2; win_y /= 2; } } MoveWindow(win32.winhandle, win_x, win_y, winrect.right - winrect.left, winrect.bottom - winrect.top, TRUE); } return 1; } int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ win32 = {}; win32.btn1 = 'Z'; win32.btn2 = 'X'; win32.update_hz = 30.f; win32.msecond_per_frame = (i64)(1000.f/win32.update_hz); win32.min_game_width = 800; win32.min_game_height = 600; win32.pref_game_width = 800; win32.pref_game_height = 600; WNDCLASSEX winclass = {}; winclass.cbSize = sizeof(WNDCLASSEX); #if RENDER_MODE == SOFTWARE winclass.style = CS_HREDRAW | CS_VREDRAW; #elif RENDER_MODE == OPENGL winclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; #endif winclass.lpfnWndProc = (WNDPROC)&Win32Proc; winclass.hInstance = hInstance; winclass.hIcon = 0; // TODO(allen): Setup this icon! winclass.hCursor = 0; winclass.lpszClassName = "TMUND-win-class"; winclass.hIconSm = 0; #if !DISABLE_CONTROLLER Win32InputSetup(); #endif ATOM classatom; classatom = RegisterClassEx(&winclass); if (!classatom){ WINERROR("Failed to setup window class"); return 0; } RECT winrect = {}; winrect.right = win32.min_game_width; winrect.bottom = win32.min_game_height; if (!AdjustWindowRect(&winrect, WS_OVERLAPPEDWINDOW, 0)){ WINERROR("Failed to compute window size"); return 0; } HWND winhandle; winhandle = CreateWindow(winclass.lpszClassName, "THE MOST UNDEAD", //NOTE(allen): Title WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE , CW_USEDEFAULT, CW_USEDEFAULT, winrect.right - winrect.left, winrect.bottom - winrect.top, 0, 0, hInstance, 0 ); win32.min_width = winrect.right - winrect.left; win32.min_height = winrect.bottom - winrect.top; if (!winhandle){ WINERROR("Failed to setup window"); return 0; } win32.winhandle = winhandle; win32.render_vars.hdc = GetDC(win32.winhandle); render_startup(&win32.render_vars); //ReleaseDC(win32.winhandle, win32.render_vars.hdc); GetClientRect(winhandle, &winrect); render_set_screen_size(&win32.render_vars, winrect.right - winrect.left, winrect.bottom - winrect.top, win32.min_game_width, win32.min_game_height); #if RENDER_MODE == SOFTWARE ShowWindow(win32.winhandle, SW_SHOW); UpdateWindow(win32.winhandle); #endif win32.samples_per_second = 48000; win32.bytes_per_sample = sizeof(i16)*2; win32.sound_buffer_size = win32.samples_per_second*win32.bytes_per_sample; win32.sound_safety_margin = (i32) (win32.samples_per_second*win32.bytes_per_sample/(win32.update_hz*3.f)); win32.sound_safety_margin = LargeRoundUp(win32.sound_safety_margin, win32.bytes_per_sample); Win32SoundSetup(win32.samples_per_second, win32.sound_buffer_size); Win32ClearSound(); win32.sound_buffer->Play(0, 0, DSBPLAY_LOOPING); win32.samples = (i16*)VirtualAlloc(0, win32.sound_buffer_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!win32.samples){ WINERROR("Failed to setup audio buffer memory"); return 0; } Game_Memory memory; memory.size = Mbytes(256); memory.data = VirtualAlloc(0, memory.size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); win32.memory = memory; bool32 good_sound = 0; win32.keep_playing = 1; timeBeginPeriod(1); win32.first = 1; MSG msg; i64 prev_work_time = Win32GetTime(); i64 flip_time = prev_work_time; i64 start_time = prev_work_time; i64 end_time = prev_work_time; AllowLocal(start_time); AllowLocal(end_time); while (win32.keep_playing){ win32.toggle_full_screen = 0; win32.keyboard_input.key_input = 0; while (PeekMessageA(&msg, winhandle, 0, 0, PM_REMOVE)){ TranslateMessage(&msg); DispatchMessage(&msg); } #if !DISABLE_CONTROLLER Win32ControllerData controller = {}; if (win32.xin.is_valid){ // TODO(allen): Multiple controller support? Win32ControllerGet(&controller, 0); } if (controller.plugged_in){ if (controller.lstick_x != 0.f || controller.lstick_y != 0.f){ win32.controller_input.is_analog = 1; win32.controller_input.analog.stick_x = controller.lstick_x; win32.controller_input.analog.stick_y = -controller.lstick_y; } else{ win32.controller_input.is_analog = 0; win32.controller_input.digital.right = controller.right; win32.controller_input.digital.left = controller.left; win32.controller_input.digital.up = controller.up; win32.controller_input.digital.down = controller.down; } win32.controller_input.button[0] = controller.a; win32.controller_input.button[1] = controller.b; win32.controller_input.button[2] = controller.x; win32.controller_input.button[3] = controller.y; win32.controller_input.button[4] = controller.left_shoulder; win32.controller_input.button[5] = controller.right_shoulder; win32.controller_input.button[6] = controller.ltrigger; win32.controller_input.button[7] = controller.rtrigger; win32.controller_input.pause = controller.start; win32.controller_input.back = controller.back; } #endif Game_Step_Target step_target; step_target.render = render_get_render_target(&win32.render_vars); step_target.can_fullscreen = 1; step_target.is_fullscreen = &win32.is_fullscreen; step_target.audio_samples_per_second = win32.samples_per_second; step_target.second_per_frame = 1.f/win32.update_hz; step_target.btn1 = win32.btn1; step_target.btn2 = win32.btn2; bool32 was_fullscreen = win32.is_fullscreen; #define ComposeControls(name) input.name = win32.keyboard_input.name || win32.controller_input.name Game_Input input; if (win32.controller_input.is_analog){ input.is_analog = 1; input.analog.stick_x = win32.controller_input.analog.stick_x; input.analog.stick_y = win32.controller_input.analog.stick_y; } else{ input.is_analog = 0; ComposeControls(digital.up); ComposeControls(digital.down); ComposeControls(digital.left); ComposeControls(digital.right); } ComposeControls(button[0]); ComposeControls(button[1]); ComposeControls(button[2]); ComposeControls(button[3]); ComposeControls(button[4]); ComposeControls(button[5]); ComposeControls(button[6]); ComposeControls(button[7]); ComposeControls(pause); ComposeControls(back); ComposeControls(toggle_edit_mode); input.key_input = win32.keyboard_input.key_input; input.key_code = win32.keyboard_input.key_code; if (game_step(step_target, input, memory, win32.first)){ win32.keep_playing = 0; } if (win32.toggle_full_screen){ win32.is_fullscreen = !was_fullscreen; } win32.first = 0; if (win32.is_fullscreen && !was_fullscreen){ if (!Win32GoFullscreen()){ win32.is_fullscreen = 0; } } else if (!win32.is_fullscreen && was_fullscreen){ Win32GoWindowed(); } i64 audio_time = Win32GetTime(); i64 time_ellapsed_before_audio = audio_time - flip_time; DWORD play_cursor, write_cursor; if (win32.sound_buffer->GetCurrentPosition(&play_cursor, &write_cursor) == DS_OK){ if (!good_sound){ win32.sound_sample_index = write_cursor / win32.bytes_per_sample; good_sound = 1; } DWORD write_start = ((win32.sound_sample_index*win32.bytes_per_sample) % win32.sound_buffer_size); DWORD expected_bytes_per_frame = (DWORD) (win32.bytes_per_sample*win32.samples_per_second*win32.msecond_per_frame/1000); i64 useconds_til_flip = (win32.msecond_per_frame*1000 - time_ellapsed_before_audio); DWORD expected_bytes_til_flip = (DWORD) (win32.bytes_per_sample*win32.samples_per_second*useconds_til_flip/1000000); DWORD expected_frame_bounary = play_cursor + expected_bytes_til_flip; DWORD safe_cursor = write_cursor; if (safe_cursor < play_cursor){ safe_cursor += win32.sound_buffer_size; } safe_cursor += win32.sound_safety_margin; bool32 low_latency = (safe_cursor < expected_frame_bounary); DWORD target = 0; if (low_latency){ target = (expected_frame_bounary + expected_bytes_per_frame); } else{ target = (write_cursor + expected_bytes_per_frame + win32.sound_safety_margin); } target = target % win32.sound_buffer_size; DWORD write_size = 0; if (write_start > target){ write_size = win32.sound_buffer_size - write_start + target; } else{ write_size = target - write_start; } Game_Audio_Target audio_target; audio_target.samples = win32.samples; audio_target.count_per_channel = write_size / win32.bytes_per_sample; audio_target.channels = 2; audio_target.samples_per_second = win32.samples_per_second; audio_target.second_per_frame = 1.f/win32.update_hz; game_fill_samples(audio_target, memory); Win32FillSound(write_start, write_size, win32.samples); } else{ good_sound = 0; } i64 work_time = Win32GetTime(); i64 run_time = ((work_time - prev_work_time)/1000); if (run_time < win32.msecond_per_frame){ Sleep((DWORD)(win32.msecond_per_frame - run_time)); work_time = Win32GetTime(); if ((DWORD)((work_time - prev_work_time)/1000) < win32.msecond_per_frame){ // NOTE(allen): MINOR - sleep did not work } while ((DWORD)((work_time - prev_work_time)/1000) < win32.msecond_per_frame){ work_time = Win32GetTime(); } } else{ // NOTE(allen): FAIL - missed frame rate int x = 0; AllowLocal(x); } prev_work_time = Win32GetTime(); //win32.render_vars.hdc = GetDC(win32.winhandle); render_redraw_screen(&win32.render_vars); //ReleaseDC(win32.winhandle, win32.render_vars.hdc); flip_time = Win32GetTime(); } // TODO(allen): Must we do this? We know we're ending, seems like // this might be handled by OS just fine. //win32.render_vars.hdc = GetDC(win32.winhandle); render_shutdown(&win32.render_vars); //ReleaseDC(win32.winhandle, win32.render_vars.hdc); return 0; } // BOTTOM