int32_t CALLBACK wWinMain( HINSTANCE window_instance, HINSTANCE, // hPrevInst is useless LPWSTR, // not using lpCmdLine int) // not using nCmdShow { LARGE_INTEGER perf_count_freq_result; QueryPerformanceFrequency(&perf_count_freq_result); int64_t perf_count_freq = perf_count_freq_result.QuadPart; win32_load_xinput(); wchar_t *wnd_class_name = L"Handmade Hero Window Class"; WNDCLASSEXW wnd_class = {}; // c++11 aggregate init to zero the struct wnd_class.cbSize = sizeof(wnd_class); wnd_class.style = CS_HREDRAW | CS_VREDRAW; wnd_class.lpfnWndProc = win32_wnd_proc; wnd_class.hInstance = window_instance; wnd_class.lpszClassName = wnd_class_name; ATOM wnd_class_atom = RegisterClassExW(&wnd_class); if (0 == wnd_class_atom) { win32_debug_print_last_error(); HANDMADE_ASSERT(0 != wnd_class_atom); return 0; } HWND hwnd = CreateWindowExW(0, wnd_class_name, L"Handmade Hero", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, window_instance, nullptr); if (hwnd == nullptr) { win32_debug_print_last_error(); HANDMADE_ASSERT(nullptr != hwnd); return 0; } win32_resize_backbuffer(&g_backbuffer, 1280, 720); // test sound win32_sound_output sound_output {}; sound_output.running_sample_index = 0; sound_output.num_sound_ch = 2; sound_output.samples_per_sec = 48000; sound_output.sec_to_buffer = 2; sound_output.latency_sample_count = sound_output.samples_per_sec / 15; sound_output.bytes_per_sample = sizeof(int16_t) * sound_output.num_sound_ch; sound_output.sound_buffer_size = sound_output.samples_per_sec * sound_output.bytes_per_sample * sound_output.sec_to_buffer; // create buffer for 2 sec win32_init_direct_sound(hwnd, sound_output.num_sound_ch, sound_output.samples_per_sec, sound_output.sound_buffer_size); int16_t *samples = nullptr; if (g_sound_buffer) { // fill the whole buffer and started playing sound. win32_clear_sound_buffer(g_sound_buffer); g_sound_buffer->Play(0, 0, DSBPLAY_LOOPING); // allocate sound buffer sample // Guarantee to be allocation granularity (64KB) aligned // commited to page boundary (4KB), but the rest are wasted space // memory auto clears to 0 // freed automatically when app terminates samples = static_cast<int16_t*>(win32_alloc_zeroed( nullptr, sound_output.sound_buffer_size)); } // input const real32 left_thumb_norm_deadzone = win32_get_xinput_stick_normalized_deadzone( XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE); const real32 right_thumb_norm_deadzone = win32_get_xinput_stick_normalized_deadzone( XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE); game_input input[2] = {}; game_input *new_input = &input[0]; game_input *old_input = &input[1]; // game memory #if HANDMADE_INTERNAL_BUILD void *base_memory_ptr = reinterpret_cast<void*>(terabyte(2ULL)); #else void *base_memory_ptr = nullptr; #endif game_memory memory {}; memory.permanent_storage_size = megabyte(64ULL); memory.transient_storage_size = gigabyte(1ULL); uint64_t total_size = memory.permanent_storage_size + memory.transient_storage_size; // Guarantee to be allocation granularity (64KB) aligned // commited to page boundary (4KB), but the rest are wasted space // memory auto clears to 0 // freed automatically when app terminates memory.permanent_storage = win32_alloc_zeroed(base_memory_ptr, total_size); memory.transient_storage = static_cast<int8_t*>(memory.permanent_storage) + memory.permanent_storage_size; if (g_backbuffer.memory && samples && memory.permanent_storage && memory.transient_storage) { g_running = true; uint64_t last_cycle_count = __rdtsc(); LARGE_INTEGER last_perf_counter; QueryPerformanceCounter(&last_perf_counter); while (g_running) { // We don't really need old input for keyboard as all keyboard // events are processed by wm msgs. So, just copy the old state. game_controller_input *kbd_controller = get_controller(new_input, game_input::kbd_controller_index); *kbd_controller = *get_controller(old_input, game_input::kbd_controller_index); win32_process_wm_msg_synchonously(kbd_controller); if (!g_running) { break; } constexpr uint32_t max_controller_count = std::min( XUSER_MAX_COUNT, game_input::max_controller_count - 1); for (DWORD controller_index = 0; controller_index < max_controller_count; ++controller_index) { // controller index 0 is reserved for keyboard DWORD true_controller_index = controller_index + 1; XINPUT_STATE controller_state {}; game_controller_input *new_controller = get_controller(new_input, true_controller_index); // Simply get the state of the controller from XInput. if (ERROR_SUCCESS == XInputGetState(controller_index, &controller_state)) { // Controller is connected new_controller->is_connected = true; const game_controller_input *old_controller = get_controller(old_input, true_controller_index); // dwPacketNumber indicates if there have been state changes controller_state.dwPacketNumber; const XINPUT_GAMEPAD *pad = &controller_state.Gamepad; // these are what we care about win32_process_xinput_digital_button( &new_controller->move_up, &old_controller->move_up, pad, XINPUT_GAMEPAD_DPAD_UP); win32_process_xinput_digital_button( &new_controller->move_down, &old_controller->move_down, pad, XINPUT_GAMEPAD_DPAD_DOWN); win32_process_xinput_digital_button( &new_controller->move_left, &old_controller->move_left, pad, XINPUT_GAMEPAD_DPAD_LEFT); win32_process_xinput_digital_button( &new_controller->move_right, &old_controller->move_right, pad, XINPUT_GAMEPAD_DPAD_RIGHT); win32_process_xinput_digital_button( &new_controller->action_up, &old_controller->action_up, pad, XINPUT_GAMEPAD_Y); win32_process_xinput_digital_button( &new_controller->action_down, &old_controller->action_down, pad, XINPUT_GAMEPAD_A); win32_process_xinput_digital_button( &new_controller->action_left, &old_controller->action_left, pad, XINPUT_GAMEPAD_X); win32_process_xinput_digital_button( &new_controller->action_right, &old_controller->action_right, pad, XINPUT_GAMEPAD_B); win32_process_xinput_digital_button( &new_controller->left_shoulder, &old_controller->left_shoulder, pad, XINPUT_GAMEPAD_LEFT_SHOULDER); win32_process_xinput_digital_button( &new_controller->right_shoulder, &old_controller->right_shoulder, pad, XINPUT_GAMEPAD_RIGHT_SHOULDER); // bool32 start = pad->wButtons & XINPUT_GAMEPAD_START; // bool32 back = pad->wButtons & XINPUT_GAMEPAD_BACK; win32_process_xinput_digital_button( &new_controller->start, &old_controller->start, pad, XINPUT_GAMEPAD_START); win32_process_xinput_digital_button( &new_controller->back, &old_controller->back, pad, XINPUT_GAMEPAD_BACK); // process stick new_controller->is_analog = true; new_controller->left_stick.avg_x = win32_xinput_thumb_resolve_deadzone_normalize( pad->sThumbLX, left_thumb_norm_deadzone); new_controller->left_stick.avg_y = win32_xinput_thumb_resolve_deadzone_normalize( pad->sThumbLY, left_thumb_norm_deadzone); new_controller->right_stick.avg_x = win32_xinput_thumb_resolve_deadzone_normalize( pad->sThumbRX, right_thumb_norm_deadzone); new_controller->right_stick.avg_y = win32_xinput_thumb_resolve_deadzone_normalize( pad->sThumbRY, right_thumb_norm_deadzone); } else { // Controller is not connected new_controller->is_connected = false; } } // DirectSound output test uint32_t byte_to_lock = 0; uint32_t bytes_to_write = 0; if (g_sound_buffer) { DWORD play_cursor; DWORD write_cursor; if (SUCCEEDED(g_sound_buffer->GetCurrentPosition(&play_cursor, &write_cursor))) { // fill the buffer till the play cursor + latency, // start location is the last location we wrote to byte_to_lock = (sound_output.running_sample_index * sound_output.bytes_per_sample) % sound_output.sound_buffer_size; // TODO: this may be dangerous! overwriting part of memory // between play cursor and write cursor DWORD target_to_cursor = (play_cursor + sound_output.latency_sample_count * sound_output.bytes_per_sample) % sound_output.sound_buffer_size; if (byte_to_lock > target_to_cursor) { bytes_to_write = sound_output.sound_buffer_size - byte_to_lock + target_to_cursor; } else { bytes_to_write = target_to_cursor - byte_to_lock; } } } game_sound_buffer game_sound_buffer {}; if (bytes_to_write > 0) { game_sound_buffer.samples = samples; game_sound_buffer.sample_count = bytes_to_write / sound_output.bytes_per_sample; game_sound_buffer.samples_per_sec = sound_output.samples_per_sec; } game_offscreen_buffer buffer {}; buffer.width = g_backbuffer.width; buffer.height = g_backbuffer.height; buffer.pitch = g_backbuffer.pitch; buffer.memory = g_backbuffer.memory; game_update_and_render(&memory, &buffer, &game_sound_buffer, new_input); if (bytes_to_write > 0) { win32_fill_sound_buffer(&sound_output, &game_sound_buffer, byte_to_lock, bytes_to_write); } HDC device_context = GetDC(hwnd); win32_window_dimension dimension = win32_get_window_dimension(hwnd); win32_display_offscreen_buffer(&g_backbuffer, device_context, dimension.width, dimension.height); ReleaseDC(hwnd, device_context); // swap game input game_input *tmp_input = new_input; new_input = old_input; old_input = tmp_input; uint64_t end_cycle_count = __rdtsc(); LARGE_INTEGER end_perf_counter; QueryPerformanceCounter(&end_perf_counter); // use signed, as it may go backward int64_t cycles_elapsed = end_cycle_count - last_cycle_count; int64_t counter_elapsed = end_perf_counter.QuadPart - last_perf_counter.QuadPart; real32 mega_cycles_per_frame = static_cast<real32>(cycles_elapsed) / 1000000.0f; real32 ms_per_frame = 1000.0f * static_cast<real32>(counter_elapsed) / static_cast<real32>(perf_count_freq); real32 fps = static_cast<real32>(perf_count_freq) / static_cast<real32>(counter_elapsed); // char buf[256]; // sprintf_s(buf, sizeof(buf), "%.2f Mc/f, %.2f ms/f, %.2f fps\n", // mega_cycles_per_frame, ms_per_frame, fps); // OutputDebugStringA(buf); last_perf_counter = end_perf_counter; last_cycle_count = end_cycle_count; } } else { // fail to allocate memory, no game. OutputDebugStringA("Fail to alloc memory to backbuffer, sound buffer, " "or game memory.\n"); } // MessageBoxW(nullptr, L"hello world", L"hello", MB_OK | MB_ICONINFORMATION); return 0; }
void GONG_CORE_PREFIX(retro_run)(void) { int i = 0; int port = 0; bool updated = false; retro_inputs inputs[MAX_PLAYERS] = {{0}}; if (GONG_CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(); GONG_CORE_PREFIX(input_poll_cb)(); for (port = 0; port < MAX_PLAYERS; port++) { for (i = 0; i < 16; i++) { if (GONG_CORE_PREFIX(input_state_cb)(port, RETRO_DEVICE_JOYPAD, 0, i)) { inputs[port].realinput |= 1 << i; } } inputs[port].analogYLeft = GONG_CORE_PREFIX(input_state_cb)(port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y) / 5000.0f; inputs[port].analogYRight = GONG_CORE_PREFIX(input_state_cb)(port, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y) / 5000.0f; if (inputs[port].analogYLeft > 0) inputs[port].realinput |= (1 << RETRO_DEVICE_ID_JOYPAD_DOWN); else if (inputs[port].analogYRight > 0) inputs[port].realinput |= (1 << RETRO_DEVICE_ID_JOYPAD_DOWN); if (inputs[port].analogYLeft < 0) inputs[port].realinput |= (1 << RETRO_DEVICE_ID_JOYPAD_UP); else if (inputs[port].analogYRight < 0) inputs[port].realinput |= (1 << RETRO_DEVICE_ID_JOYPAD_UP); inputs[port].input = inputs[port].realinput & ~g_state->previnput[port]; inputs[port].not_input = g_state->previnput[port] & ~inputs[port].realinput; if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_UP)) process_joypad(&g_state->g_input[port].buttons[B_MOVE_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_UP)); else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_DOWN)) process_joypad(&g_state->g_input[port].buttons[B_MOVE_DOWN], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN)); else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_DOWN)) process_joypad(&g_state->g_input[port].buttons[B_MOVE_DOWN], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_DOWN)); if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_A)) process_joypad(&g_state->g_input[port].buttons[B_SPEED_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_A)); else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_B)) process_joypad(&g_state->g_input[port].buttons[B_SPEED_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_B)); else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_X)) process_joypad(&g_state->g_input[port].buttons[B_SPEED_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_X)); else if (is_key_up_or_down(inputs[port].input, inputs[port].not_input, RETRO_DEVICE_ID_JOYPAD_Y)) process_joypad(&g_state->g_input[port].buttons[B_SPEED_UP], inputs[port].realinput & (1 << RETRO_DEVICE_ID_JOYPAD_Y)); g_state->previnput[port] = inputs[port].realinput; } game_update_and_render(g_state->g_input, &game_buffer); GONG_CORE_PREFIX(video_cb)(video_buf, WIDTH, HEIGHT, WIDTH * sizeof(uint32_t)); }
int main(int argc, char* argv[]) { SDL_Window* sdl_window; SDL_Renderer* sdl_renderer; SDL_Texture* sdl_screen_texture; SDL_AudioSpec sdl_audio_spec; SDL_GameController* sdl_controller; memory_block_t memory; memory_block_t sound_samples; input_state_t keyboard_input, controller_input, user_input; surface_t offscreen_buffer; sdl_audio_ring_buffer_t audio_ring_buffer; int32_t offscreen_buffer_byte_width; int32_t screen_width, screen_height; real32_t target_seconds_per_frame; uint64_t perf_counter_freq; uint64_t last_counter; real32_t elapsed_frame_seconds; screen_width = 640; screen_height = 480; /* initialize sdl and create window */ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_AUDIO) < 0) { /* TODO(lars): logging */ return (1); } sdl_window = SDL_CreateWindow("sdl2-start", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_width, screen_height, SDL_WINDOW_SHOWN); if (!sdl_window) { /* TODO(lars): logging */ return (1); } global_refresh_rate = sdl_get_refresh_rate(sdl_window); /* create sdl renderer */ sdl_renderer = SDL_CreateRenderer(sdl_window, -1, SDL_RENDERER_PRESENTVSYNC); if (!sdl_renderer) { sdl_renderer = SDL_CreateRenderer(sdl_window, -1, 0); if (!sdl_renderer) { /* TODO(lars): logging */ return (1); } } SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255); /* create sdl texture */ sdl_screen_texture = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, screen_width, screen_height); if (!sdl_screen_texture) { /* TODO(lars): logging */ return (1); } /* allocated needed memory */ memory.size = game_query_needed_memory_size(); memory.base = calloc(memory.size, sizeof(uint8_t)); if (!memory.base) { /* TODO(lars): logging */ return (1); } /* setup input */ memset(&keyboard_input, 0, sizeof(input_state_t)); memset(&controller_input, 0, sizeof(input_state_t)); sdl_controller = NULL; /* create offscreen_buffer */ offscreen_buffer.width = screen_width; offscreen_buffer.height = screen_height; offscreen_buffer.stride = sizeof(uint32_t); offscreen_buffer.buf = (uint32_t*)calloc(offscreen_buffer.width * offscreen_buffer.height, offscreen_buffer.stride); if (!offscreen_buffer.buf) { /* TODO(lars): logging */ return (1); } offscreen_buffer_byte_width = offscreen_buffer.width * offscreen_buffer.stride; /* set target framerate */ target_seconds_per_frame = 1.0f / TARGET_FRAMES_PER_SECOND; perf_counter_freq = SDL_GetPerformanceFrequency(); /* setup audio */ sdl_audio_spec.freq = 48000; /* 48 kHz */ sdl_audio_spec.format = AUDIO_S16LSB; sdl_audio_spec.channels = 2; sdl_audio_spec.samples = 512; sdl_audio_spec.callback = &sdl_audio_callback; sdl_audio_spec.userdata = &audio_ring_buffer; if (SDL_OpenAudio(&sdl_audio_spec, NULL) == 0 && sdl_audio_spec.format == AUDIO_S16LSB) { int32_t audio_ring_buffer_size; audio_ring_buffer_size = sdl_audio_spec.freq * sizeof(int16_t) * sdl_audio_spec.channels; audio_ring_buffer.play_index = 0; audio_ring_buffer.write_index = 0; audio_ring_buffer.size = audio_ring_buffer_size; audio_ring_buffer.data = calloc(audio_ring_buffer_size, sizeof(uint8_t)); if (audio_ring_buffer.data) { sound_samples.size = 15 * (int32_t)(target_seconds_per_frame * (real32_t)sdl_audio_spec.freq) * sdl_audio_spec.channels * sizeof(int16_t); sound_samples.base = calloc(sound_samples.size, sizeof(uint8_t)); if (sound_samples.base) { /* unpause the audio */ SDL_PauseAudio(0); } else { /* TODO(lars): logging */ free(audio_ring_buffer.data); SDL_CloseAudio(); } } else { /* TODO(lars): logging */ SDL_CloseAudio(); } } else { /* TODO(lars): logging */ SDL_CloseAudio(); } elapsed_frame_seconds = target_seconds_per_frame; last_counter = SDL_GetPerformanceCounter(); global_is_running = 1; while (global_is_running) { int32_t window_width; int32_t window_height; int32_t current_audio_buffer_size; int32_t audio_bytes_to_write; SDL_GetWindowSize(sdl_window, &window_width, &window_height); /* handle user input */ sdl_process_keyboard_input(sdl_window, &keyboard_input); sdl_process_controller_input(sdl_controller, &controller_input); user_input = merge_input_state(keyboard_input, controller_input); /* game update and render */ game_update_and_render(&memory, &user_input, &offscreen_buffer, elapsed_frame_seconds); /* get game sound samples and fill the audio ring buffer */ audio_bytes_to_write = 0; SDL_LockAudioDevice(1); if (audio_ring_buffer.write_index >= audio_ring_buffer.play_index) { current_audio_buffer_size = (audio_ring_buffer.write_index - audio_ring_buffer.play_index); } else { current_audio_buffer_size = (audio_ring_buffer.size - audio_ring_buffer.play_index) + audio_ring_buffer.write_index; } SDL_UnlockAudioDevice(1); if (current_audio_buffer_size < sound_samples.size) { audio_bytes_to_write = sound_samples.size - current_audio_buffer_size; } game_get_sound_samples(&sound_samples, audio_bytes_to_write / (sizeof(int16_t) * sdl_audio_spec.channels), sdl_audio_spec.freq, sdl_audio_spec.channels); sdl_fill_audio_ring_buffer(&audio_ring_buffer, &sound_samples, audio_bytes_to_write); /* upload the offscreen buffer to the SDL_Renderer */ SDL_RenderClear(sdl_renderer); if (SDL_UpdateTexture(sdl_screen_texture, 0, offscreen_buffer.buf, offscreen_buffer_byte_width) != 0) { /* TODO(lars): logging */ return (1); } SDL_RenderCopyEx(sdl_renderer, sdl_screen_texture, NULL, NULL, 0, NULL, SDL_FLIP_VERTICAL); /* enforce target game framerate by sleeping */ elapsed_frame_seconds = sdl_get_seconds_elapsed(last_counter, perf_counter_freq); if (elapsed_frame_seconds < target_seconds_per_frame) { int32_t ms_to_sleep; ms_to_sleep = (int32_t)((target_seconds_per_frame - elapsed_frame_seconds) * 1000.0f) - 1; if (ms_to_sleep > 0) { SDL_Delay(ms_to_sleep); } while (sdl_get_seconds_elapsed(last_counter, perf_counter_freq) < target_seconds_per_frame) {}; } elapsed_frame_seconds = sdl_get_seconds_elapsed(last_counter, perf_counter_freq); /* display the frame */ SDL_RenderPresent(sdl_renderer); last_counter = SDL_GetPerformanceCounter(); } SDL_CloseAudio(); SDL_GameControllerClose(sdl_controller); SDL_DestroyWindow(sdl_window); SDL_Quit(); free(sound_samples.base); free(audio_ring_buffer.data); free(offscreen_buffer.buf); free(memory.base); return (0); }