/* TODO: Does SDL3 still need this? */ #if OS_WINDOWS /* Remove SDL's entry point. */ #define SDL_MAIN_HANDLED #endif #include "config.h" #include "helpers.h" #include "handmade.h" #include "sdl_handmade.h" #include #include /* malloc, calloc */ #include /* memset, memcpy */ #include #include /* fstat */ #include /* open */ #include /* stat */ #if OS_MACOS || OS_LINUX #include /* dlopen */ #include /* mmap */ #include /* close, write, read, readlink, unlink */ #endif #if OS_WINDOWS #include /* VirtualAlloc */ #endif #if OS_MACOS #include /* _NSGetExecutablePath */ #endif /* NOTE: This is so it compiles on ARM. */ #if ARCHITECTURE_X64 || ARCHITECTURE_X86 #if OS_MACOS || OS_LINUX #include #endif #endif #if 0 #include "program_icon.c" #endif #define RESOLUTION_WIDTH 960 #define RESOLUTION_HEIGHT 540 static int global_running; static unsigned long long global_perf_count_frequency; #define CONTROLLER_LEFT_THUMB_DEADZONE 8000 #define MAX_CONTROLLERS 4 SDL_Gamepad *controller_handles[MAX_CONTROLLERS]; static struct offscreen_buffer global_backbuffer; SDL_Window *last_window; static int is_full_screen; static struct sdl_window_size window_size; static struct sdl_window_position window_position; static int str_len(char *str) { int count = 0; while(*str++) count++; return count; } static unsigned int safe_truncate_uint64(unsigned long long value) { unsigned int result; assert(value <= 0xFFFFFFFF); result = (unsigned int)value; return result; } struct debug_read_file_result debug_platform_read_entire_file(struct thread_context *thread, const char *file_name) { struct debug_read_file_result result; SDL_IOStream *f; memset(&result, 0, sizeof(result)); f = SDL_IOFromFile(file_name, "r"); if(f) { long long f_size; unsigned int bytes_read; f_size = SDL_GetIOSize(f); if(f_size >= 0) { result.contents_size = safe_truncate_uint64(f_size); } else { result.contents_size = 0; SDL_CloseIO(f); return result; } result.contents = malloc(result.contents_size); if(!result.contents) { result.contents_size = 0; SDL_CloseIO(f); return result; } while(f_size > 0) { bytes_read = SDL_ReadIO(f, (void *)result.contents, f_size); if(bytes_read == 0) { result.contents_size = 0; SDL_CloseIO(f); return result; } f_size -= bytes_read; } } return result; } int debug_platform_write_entire_file(struct thread_context *thread, const char *file_name, unsigned int memory_size, void *memory) { SDL_IOStream *f; f = SDL_IOFromFile(file_name, "w"); if(f) { unsigned int bytes_written; bytes_written = SDL_WriteIO(f, memory, memory_size); if(bytes_written < memory_size) { SDL_CloseIO(f); return 0; } SDL_CloseIO(f); return 1; } return 0; } void debug_platform_free_file_memory(struct thread_context *Thread, void *memory) { if(memory) free(memory); } void SetProgramIcon(struct game_offscreen_buffer *program_icon) { SDL_Window *window = last_window; SDL_Surface *icon = SDL_CreateSurfaceFrom(program_icon->width, program_icon->height, SDL_PIXELFORMAT_ARGB8888, (void *)program_icon->memory, program_icon->bytes_per_pixel * program_icon->width); if(!icon) { /* TODO: This didn't work . . . SDL_GetError() */ return ; } SDL_SetWindowIcon(window, icon); SDL_DestroySurface(icon); } static struct sdl_border_size sdl_get_window_border_sizes(SDL_Window *window) { struct sdl_border_size result; SDL_GetWindowBordersSize(window, &result.top, &result.left, &result.bottom, &result.right); return result; } /* NOTE: All SDL window functions (SDL_GetWindowSize, SDL_GetWindowPosition, * SDL_SetWindowPosition, SDL_SetWindowSize) work ONLY with client area. */ static struct sdl_window_size sdl_get_bordered_window_size(SDL_Window *window) { struct sdl_border_size border = sdl_get_window_border_sizes(window); struct sdl_window_size dimension; SDL_GetWindowSize(window, &dimension.width, &dimension.height); #if OS_WINDOWS /* TODO: Check if this is still dysfunctional/inconsistent in SDL3. */ /* On Windows, borders must be added. */ dimension.Height += border.Top + border.Bottom; #endif /* dimension.Width += border.Left + border.Right; */ return dimension; } static struct sdl_window_position sdl_get_bordered_window_position(SDL_Window *window) { struct sdl_border_size border = sdl_get_window_border_sizes(window); struct sdl_window_position position; SDL_GetWindowPosition(window, &position.x, &position.y); /* position.X -= border.Left; */ position.y -= border.top; return position; } static void sdl_set_bordered_window_position(SDL_Window *window, int x, int y) { struct sdl_border_size border = sdl_get_window_border_sizes(window); SDL_SetWindowPosition(window, x, y + border.top); } static void sdl_set_bordered_window_size(SDL_Window *window, int width, int height) { struct sdl_border_size border = sdl_get_window_border_sizes(window); /* SDL_SetWindowSize(window, width - (border.Left + border.Right), height - (border.Top + border.Bottom)); */ SDL_SetWindowSize(window, width, height - (border.top + border.bottom)); } static void sdl_resize_texture(struct offscreen_buffer *buffer, SDL_Renderer *renderer, int width, int height) { int bytes_per_pixel; if(buffer->texture) SDL_DestroyTexture(buffer->texture); if(buffer->memory) free(buffer->memory); bytes_per_pixel = 4; buffer->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height); buffer->memory = malloc(width * height * bytes_per_pixel); buffer->height = height; buffer->width = width; buffer->pitch = width * bytes_per_pixel; } static void sdl_display_buffer_in_window(struct offscreen_buffer *buffer, SDL_Window *window, SDL_Renderer *renderer) { int offset_x, offset_y; struct sdl_window_size win_size; offset_x = 10; offset_y = 10; SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); if(SDL_UpdateTexture(buffer->texture, 0, buffer->memory, buffer->pitch)) { /* TODO: Do something about this error! */ } /* TODO: We temporarily introduce the target rectangle to avoid * stretching the canvas. */ /* SDL_RenderCopy(Renderer, Buffer.Texture, 0, 0); */ win_size = sdl_get_bordered_window_size(window); if(win_size.width >= buffer->width*2 && win_size.height >= buffer->height*2) { SDL_FRect dest_rect; dest_rect.x = (float)offset_x; dest_rect.y = (float)offset_y; dest_rect.w = (float)(buffer->width*2); dest_rect.h = (float)(buffer->height*2); SDL_RenderTexture(renderer, buffer->texture, 0, (const SDL_FRect *)(&dest_rect)); } else { SDL_FRect dest_rect; dest_rect.x = (float)offset_x; dest_rect.y = (float)offset_y; dest_rect.w = (float)RESOLUTION_WIDTH; dest_rect.h = (float)RESOLUTION_HEIGHT; SDL_RenderTexture(renderer, buffer->texture, 0, (const SDL_FRect *)(&dest_rect)); } SDL_RenderPresent(renderer); } static void sdl_init_controllers() { int max_joysticks, controller_index; SDL_JoystickID *gamepads; int joystick_index; controller_index = 0; gamepads = SDL_GetJoysticks(&max_joysticks); /* TODO: Why would we fetch this by instance? Can we fetch available * ones? */ for(joystick_index = 0; joystick_index < max_joysticks; joystick_index++) { if(!SDL_IsGamepad(gamepads[joystick_index])) continue; if(controller_index >= MAX_CONTROLLERS) break; controller_handles[controller_index] = SDL_OpenGamepad(gamepads[joystick_index]); controller_index++; } } /* static void sdl_deinit_controllers() { int controller_index; for(controller_index = 0; controller_index < MAX_CONTROLLERS; controller_index++) { if(controller_handles[controller_index]) SDL_GameControllerClose(controller_handles[controller_index]); } } */ #if 0 static void sdl_init_sound(int samples_per_second, int buffer_size) { SDL_AudioSpec audio_settings; memset(&audio_settings, 0, sizeof(audio_settings)); audio_settings.freq = samples_per_second; audio_settings.format = SDL_AUDIO_S16LE; audio_settings.channels = 2; /* TODO: Unsafe truncate from S32 to U16 */ audio_settings.samples = (unsigned short)buffer_size; SDL_OpenAudio(&audio_settings, 0); if(audio_settings.format != AUDIO_S16LSB) { SDL_CloseAudio(); } } #endif #if 0 static void sdl_fill_sound_buffer(struct sdl_sound_output *sound_output, int bytes_to_write) { SDL_QueueAudio(1, sound_output->Samples, bytes_to_write); } static void sdl_clear_sound_buffer(struct sdl_sound_output *sound_output) { memset(sound_output->Samples, 0, sound_output->SecondaryBufferSize); } #endif static void sdl_process_keyboard_message(struct game_button_state *new_state, int is_down) { if(new_state->ended_down != is_down) { new_state->ended_down = is_down; ++new_state->half_transition_count; } } static void sdl_process_input_digital_button(SDL_Gamepad *controller, struct game_button_state *old_state, SDL_GamepadButton button, struct game_button_state *new_state) { new_state->ended_down = SDL_GetGamepadButton(controller, button); new_state->half_transition_count = (old_state->ended_down != new_state->ended_down) ? 1 : 0; } static void HandleEvent(SDL_Event *event) { SDL_Window *window = SDL_GetWindowFromID(event->window.windowID); SDL_Renderer *renderer = SDL_GetRenderer(window); switch(event->type) { case SDL_EVENT_QUIT: { global_running = 0; } break; /* TODO: We temporarily disable for ease of writing the * rendering code. */ #if 0 /* TODO: For now we fix the width and height. */ case SDL_EVENT_WINDOW_RESIZED: { sdl_window_dimension dimension = SDLGetWindowDimension(window); SDLResizeTexture(&global_backbuffer, renderer, dimension.width, dimension.height); } break; #endif case SDL_EVENT_WINDOW_EXPOSED: { } break; #if 0 case SDL_EVENT_WINDOW_FOCUS_GAINED: { if(SDL_SetWindowOpacity(window, 1.0f) != 0) { /* TODO: This didn't work . . . SDL_GetError() */ } } break; case SDL_EVENT_WINDOW_FOCUS_LOST: { if(SDL_SetWindowOpacity(window, 0.1f) != 0) { /* TODO: This didn't work . . . SDL_GetError() */ } } break; #endif } } static void sdl_toggle_fullscreen(SDL_Window *window) { bool ok; if(is_full_screen) { ok = SDL_SetWindowFullscreen(window, false); if(ok) { sdl_set_bordered_window_size(window, window_size.width, window_size.height); sdl_set_bordered_window_position(window, window_position.x, window_position.y); is_full_screen = 0; } else { /* TODO: This didn't work . . . SDL_GetError() */ } } else { window_size = sdl_get_bordered_window_size(window); window_position = sdl_get_bordered_window_position(window); ok = SDL_SetWindowFullscreen(window, true); if(ok) { is_full_screen = 1; } else { /* TODO: This didn't work . . . SDL_GetError() */ } } } static void sdl_process_messages(struct sdl_state *sdl_state, struct game_controller_input *keyboard_controller) { SDL_Event event; while(SDL_PollEvent(&event)) { SDL_Window *window = SDL_GetWindowFromID(event.window.windowID); SDL_Renderer *renderer = SDL_GetRenderer(window); switch(event.type) { case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_UP: { SDL_Keycode key_code; int is_down, was_down; key_code = event.key.key; is_down = event.key.down; was_down = 0; if(!event.key.down) { was_down = 1; } else if(event.key.repeat != 0) { was_down = 1; } if(event.key.repeat == 0) { if(key_code == SDLK_W) { sdl_process_keyboard_message( &keyboard_controller->u.s.move_up, is_down); } else if(key_code == SDLK_S) { sdl_process_keyboard_message( &keyboard_controller->u.s.move_down, is_down); } else if(key_code == SDLK_A) { sdl_process_keyboard_message( &keyboard_controller->u.s.move_left, is_down); } else if(key_code == SDLK_D) { sdl_process_keyboard_message( &keyboard_controller->u.s.move_right, is_down); } else if(key_code == SDLK_Q) { sdl_process_keyboard_message( &keyboard_controller->u.s.left_shoulder, is_down); } else if(key_code == SDLK_E) { sdl_process_keyboard_message( &keyboard_controller->u.s.right_shoulder, is_down); } else if(key_code == SDLK_UP) { sdl_process_keyboard_message( &keyboard_controller->u.s.action_up, is_down); } else if(key_code == SDLK_DOWN) { sdl_process_keyboard_message( &keyboard_controller->u.s.action_down, is_down); } else if(key_code == SDLK_LEFT) { sdl_process_keyboard_message( &keyboard_controller->u.s.action_left, is_down); } else if(key_code == SDLK_RIGHT) { sdl_process_keyboard_message( &keyboard_controller->u.s.action_right, is_down); } else if(key_code == SDLK_ESCAPE) { sdl_process_keyboard_message( &keyboard_controller->u.s.back, is_down); } else if(key_code == SDLK_SPACE) { sdl_process_keyboard_message( &keyboard_controller->u.s.start, is_down); } } if(was_down) { /* NOTE: If your window manager already uses an Alt+F4 * keybind to close programs, then we are likely * to see SDL_QUIT event occur before our keybind. */ int alt_key_was_down = (event.key.mod & SDL_KMOD_ALT); if(key_code == SDLK_F4 && alt_key_was_down) global_running = 0; #if BUILD_INTERNAL if(key_code == SDLK_ESCAPE) global_running = 0; #endif if((key_code == SDLK_RETURN) && alt_key_was_down) { sdl_toggle_fullscreen(window); } } } break; default: { HandleEvent(&event); } break; } } } static float sdl_process_input_stick_value(float value, int dead_zone_threshold) { float result = 0.0f; if(value < -(float)dead_zone_threshold) result = (value + (float)dead_zone_threshold) / (32768.0f - (float)dead_zone_threshold); else if(value > (float)dead_zone_threshold) result = (value + (float)dead_zone_threshold) / (32767.0f - (float)dead_zone_threshold); return result; } #define DEFAULT_REFRESH_RATE 60 static int sdl_get_window_refresh_rate(SDL_Window *window) { unsigned int display_index; const SDL_DisplayMode *mode; display_index = SDL_GetDisplayForWindow(window); mode = SDL_GetDesktopDisplayMode(display_index); if(!mode) return DEFAULT_REFRESH_RATE; if(mode->refresh_rate == 0) return DEFAULT_REFRESH_RATE; return mode->refresh_rate; } static unsigned long long sdl_get_wall_clock() { return SDL_GetPerformanceCounter(); } static float sdl_get_seconds_elapsed(unsigned long long start, unsigned long long end) { return (float)(end - start) / (float)global_perf_count_frequency; } struct sdl_game_code { void *game_code_dll; unsigned int dll_last_write_time; void (*update_and_render)(struct thread_context *Thread, struct game_memory *, struct game_input *, struct game_offscreen_buffer *, struct game_sound_output_buffer *); int is_valid; }; static unsigned int get_last_write_time(const char *file_name) { struct stat file_info; memset(&file_info, 0, sizeof(file_info)); if(stat(file_name, &file_info) != 0) { /* TODO: Diagnostic. perror("stat failed"); */ } return (unsigned int)file_info.st_mtime; } #if OS_MACOS || OS_LINUX #define GAME_DLL_NAME "libhandmade.so" #elif OS_WINDOWS #define GAME_DLL_NAME "handmade.dll" #endif static struct sdl_game_code sdl_load_game_code(char *dll_path) { struct sdl_game_code result; memset(&result, 0, sizeof(result)); #if OS_MACOS || OS_LINUX result.game_code_dll = dlopen(dll_path, RTLD_NOW); #elif OS_WINDOWS result.game_code_dll = LoadLibraryA(dll_path); #else #error Unknown OS: implement DLL loading... #endif if(result.game_code_dll) { result.dll_last_write_time = get_last_write_time(dll_path); #if OS_MACOS || OS_LINUX result.update_and_render = (void(*)(struct thread_context *, struct game_memory *, struct game_input *, struct game_offscreen_buffer *, struct game_sound_output_buffer *))dlsym(result.game_code_dll, "update_and_render"); #elif OS_WINDOWS result.update_and_render = (void(*)(thread_context *, game_memory *, game_input *, game_offscreen_buffer *, game_sound_output_buffer *))GetProcAddress(result.game_code_dll, "update_and_render"); #else #error Unknown OS: implement DLL loading... #endif if(!result.update_and_render) { /* TODO: Diagnostic. dlerror() */ } result.is_valid = result.update_and_render ? 1 : 0; } else { /* TODO: Diagnostic. dlerror() */ } if(!result.is_valid) { result.update_and_render = 0; } return result; } static void sdl_unload_game_code(struct sdl_game_code *game_code) { if(game_code->game_code_dll) { #if OS_MACOS || OS_LINUX dlclose(game_code->game_code_dll); /* NOTE: Might fail. */ #elif OS_WINDOWS FreeLibrary(game_code->game_code_dll); #else #error Unknown OS: implement closing of DLL #endif } game_code->is_valid = 0; game_code->update_and_render = 0; } void get_executable_path(char *path, size_t path_size) { #if OS_MACOS unsigned int size; #elif OS_LINUX ssize_t size; #endif if(path_size > 0) { path[0] = '\0'; #if OS_MACOS size = path_size; if(_NSGetExecutablePath(path, &size) != 0) { /* Not an absolute path. */ /* TODO: Diagnostics. */ } #elif OS_LINUX size = readlink("/proc/self/exe", path, path_size - 1); if(size != -1) { path[size] = '\0'; } else { /* TODO: Diagnostics. */ } #elif OS_WINDOWS GetModuleFileName(NULL, path, (DWORD)path_size); // TODO: hm... #endif } } static void sdl_get_exe_dir_path(char *path, size_t path_size) { char delimeter; unsigned int len, i; #if OS_MACOS || OS_LINUX delimeter = '/'; #elif OS_WINDOWS delimeter = '\\'; #endif get_executable_path(path, path_size); len = str_len(path); i = len; while(path[i] != delimeter || i == 0) { i--; } path[i+1] = '\0'; } static void sdl_get_dll_path(char *path, size_t path_size, char *exe_path) { snprintf(path, path_size, "%s%s", exe_path, GAME_DLL_NAME); } #if !(ARCHITECTURE_X64 || ARCHITECTURE_X86) U64 __rdtsc() { return 0; } #endif #include "profiler.h" int main(int argc, char *argv[]) { struct sdl_state sdl_state; SDL_Window *window; int i; memset(&sdl_state, 0, sizeof(sdl_state)); sdl_get_exe_dir_path(sdl_state.exe_dir_path, sizeof(sdl_state.exe_dir_path)); sdl_get_dll_path(sdl_state.dll_path, sizeof(sdl_state.dll_path), sdl_state.exe_dir_path); if(!SDL_Init(SDL_INIT_VIDEO)) { /* TODO: This didn't work . . . */ } /* NOTE: It takes roughly half a second to initialize * SDL_INIT_GAMEPAD. */ if(!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) { /* TODO: This didn't work . . . */ } sdl_init_controllers(); /* NOTE: Create hidden window so that a blank window doesn't appear * before everything is ready. */ /* TODO: SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, in non-debug * build */ window = SDL_CreateWindow("Handmade Hero", RESOLUTION_WIDTH+20, RESOLUTION_HEIGHT+20, SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY /*| SDL_WINDOW_ALWAYS_ON_TOP*/); if(window) { const SDL_DisplayMode *display_mode; SDL_Renderer *renderer; last_window = window; display_mode = SDL_GetCurrentDisplayMode(0); renderer = SDL_CreateRenderer(window, 0); SDL_SetRenderVSync(renderer, SDL_RENDERER_VSYNC_ADAPTIVE); /* NOTE: For future reference, the custom window bar should use * SDL_SetWindowHitTest to define areas that will be used to drag * the window. */ /* "Wayland needs an event loop and rendering or it won't function." * https://github.com/libsdl-org/SDL/issues/7699#issuecomment-1545684792 */ /* NOTE: This means that we need to readraw to even resize the * window, otherwise the window frame will appear outdated to * its actual size. */ /* SDL_RenderPresent(renderer) is enough to display the window. */ if(renderer) { struct thread_context temp_context; SDL_Cursor *pointer_cursor; SDL_Rect usable_display_rect; struct sdl_window_size current_window_size; struct sdl_sound_output sound_output; #if BUILD_DEBUG void *base_address = (void *)TB(2); #else void *base_address = (void *)(0); #endif struct game_memory game_memory; memset(&temp_context, 0, sizeof(temp_context)); /* TODO: Make it a global. */ pointer_cursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT); SDL_SetCursor(pointer_cursor); #if !BUILD_INTERNAL if(SDL_ShowCursor(SDL_DISABLE) < 0) { /* TODO: This didn't work . . . SDL_GetError() */ } #endif SDL_GetDisplayUsableBounds(0, &usable_display_rect); current_window_size = sdl_get_bordered_window_size(window); sdl_set_bordered_window_position(window, (usable_display_rect.x + usable_display_rect.w - current_window_size.width), (usable_display_rect.y + usable_display_rect.h - current_window_size.height)); global_running = 1; /* sdl_window_dimension Dimension = SDLGetWindowDimension(window); */ /* sdl_resize_texture(&global_backbuffer, renderer, Dimension.Width, Dimension.Height); */ sdl_resize_texture(&global_backbuffer, renderer, RESOLUTION_WIDTH, RESOLUTION_HEIGHT); memset(&sound_output, 0, sizeof(sound_output)); sound_output.samples_per_second = 48000; sound_output.bytes_per_sample = sizeof(short) * 2; sound_output.secondary_buffer_size = sound_output.samples_per_second * sound_output.bytes_per_sample; sound_output.latency_sample_count = sound_output.samples_per_second / 15; #if 0 if(!SDL_InitSubSystem(SDL_INIT_AUDIO)) { /* TODO: This didn't work . . . */ } sdl_init_sound(sound_output.SamplesPerSecond, sound_output.SamplesPerSecond * sound_output.BytesPerSample / 60); /* TODO: Should be paused until we have sound to play. */ SDL_PauseAudio(0); sound_output.Samples = calloc(sound_output.SamplesPerSecond, sound_output.BytesPerSample); /*sound_output.Samples = malloc(sound_output.SecondaryBufferSize);*/ /* NOTE: calloc auto clears to zero */ /* sdl_clear_sound_buffer(&sound_output); */ #endif memset(&game_memory, 0, sizeof(game_memory)); game_memory.permanent_storage_size = MB(64); game_memory.transient_storage_size = GB(1); game_memory.debug_platform_read_entire_file = &debug_platform_read_entire_file; game_memory.SetProgramIcon = &SetProgramIcon; /* game_memory.DEBUGPlatformWriteEntireFile = &debug_platform_write_entire_file; */ game_memory.debug_platform_free_file_memory = &debug_platform_free_file_memory; sdl_state.total_size = game_memory.permanent_storage_size + game_memory.transient_storage_size; #if OS_MACOS || OS_LINUX /* NOTE: On MacOS and Linux, mmap seems to zero-fill anonymous * memory as a side-effect of security. */ sdl_state.game_memory_block = mmap(base_address, (size_t)sdl_state.total_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); #elif OS_WINDOWS sdl_state.GameMmemoryBlock = VirtualAlloc(base_address, sdl_state.TotalSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); #else #error Unknown OS: please, allocate memory for arena #endif game_memory.permanent_storage = sdl_state.game_memory_block; game_memory.transient_storage = (unsigned char *)(game_memory.permanent_storage) + game_memory.permanent_storage_size; if(/*sound_output.Samples &&*/ game_memory.permanent_storage && game_memory.transient_storage) { struct game_input input[2]; unsigned long long last_counter; unsigned long long last_cycle_count; int monitor_refresh_hz, game_update_hz; float target_seconds_per_frame; struct game_input *new_input, *old_input; struct sdl_game_code game; unsigned long long fps_last_counter; monitor_refresh_hz = sdl_get_window_refresh_rate(window); /*game_update_hz = monitor_refresh_hz;*/ game_update_hz = 30; /* NOTE: Temporarily target 30 FPS. */ target_seconds_per_frame = 1.0f / (float)game_update_hz; new_input = &input[0]; old_input = &input[1]; memset(input, 0, sizeof(input)); global_perf_count_frequency = SDL_GetPerformanceFrequency(); game = sdl_load_game_code(sdl_state.dll_path); /* Everything is ready; display the window.*/ SDL_ShowWindow(window); fps_last_counter = sdl_get_wall_clock(); while(global_running) { unsigned int new_dll_write_time; float mouse_x, mouse_y; int window_x, window_y; unsigned int sdl_mouse_buttons; struct game_controller_input *old_keyboard_controller, *new_keyboard_controller; struct game_controller_input zero_controller; unsigned int button_index; unsigned int controller_index; int target_queue_bytes; int bytes_to_write; struct game_sound_output_buffer sound_buffer; struct thread_context context; struct game_offscreen_buffer buffer; unsigned long long work_counter; float work_seconds_elapsed; float seconds_elapsed_for_frame; unsigned long long end_counter; float ms_per_frame; float fps; new_input->dt_for_frame = target_seconds_per_frame; last_counter = sdl_get_wall_clock(); /* NOTE: rdtsc reports clock cylces, however it is not * meant for really precise profiler work as the value * returned is varied. Also, __rdtsc is not available * on MacOS ARM; I have not checked MacOS x64. */ last_cycle_count = __rdtsc(); new_dll_write_time = get_last_write_time(sdl_state.dll_path); if(new_dll_write_time > game.dll_last_write_time) { sdl_unload_game_code(&game); game = sdl_load_game_code(sdl_state.dll_path); } SDL_GetGlobalMouseState(&mouse_x, &mouse_y); SDL_GetWindowPosition(window, &window_x, &window_y); new_input->mouse_x = (int)mouse_x - window_x; new_input->mouse_y = (int)mouse_y - window_y; sdl_mouse_buttons = SDL_GetMouseState(NULL, NULL); new_input->mouse_z = 0; /* TODO: Support mouse wheel? */ sdl_process_keyboard_message(&(new_input->mouse_buttons[0]), SDL_BUTTON_LMASK & sdl_mouse_buttons); sdl_process_keyboard_message(&(new_input->mouse_buttons[1]), SDL_BUTTON_MMASK & sdl_mouse_buttons); sdl_process_keyboard_message(&(new_input->mouse_buttons[2]), SDL_BUTTON_RMASK & sdl_mouse_buttons); /* WARNING: TODO: SDL_BUTTON_X1MASK and SDL_BUTTON_X2MASK * cannot be on at the same time for some reason. */ sdl_process_keyboard_message(&(new_input->mouse_buttons[3]), SDL_BUTTON_X1MASK & sdl_mouse_buttons); sdl_process_keyboard_message(&(new_input->mouse_buttons[4]), SDL_BUTTON_X2MASK & sdl_mouse_buttons); old_keyboard_controller = GetController(old_input, 0); new_keyboard_controller = GetController(new_input, 0); memset(&zero_controller, 0, sizeof(zero_controller)); *new_keyboard_controller = zero_controller; new_keyboard_controller->is_connected = 1; for(button_index = 0; button_index < ARRAY_SIZE(new_keyboard_controller->u.buttons); button_index++) { new_keyboard_controller->u. buttons[button_index].ended_down = old_keyboard_controller->u. buttons[button_index].ended_down; } sdl_process_messages(&sdl_state, new_keyboard_controller); for(controller_index = 0; controller_index < MAX_CONTROLLERS; controller_index++) { struct game_controller_input *old_controller, *new_controller; old_controller = GetController(old_input, controller_index+1); new_controller = GetController(new_input, controller_index+1); #if 1 if(controller_handles[controller_index] != 0 && SDL_GamepadConnected( controller_handles[controller_index])) { short stick_x, stick_y; float threshold; new_controller->is_analog = old_controller->is_analog; new_controller->is_connected = 1; stick_x = SDL_GetGamepadAxis( controller_handles[controller_index], SDL_GAMEPAD_AXIS_LEFTX); stick_y = SDL_GetGamepadAxis( controller_handles[controller_index], SDL_GAMEPAD_AXIS_LEFTY); new_controller->is_analog = 1; new_controller->stick_average_x = sdl_process_input_stick_value((float)stick_x, CONTROLLER_LEFT_THUMB_DEADZONE); new_controller->stick_average_y = sdl_process_input_stick_value((float)stick_y, CONTROLLER_LEFT_THUMB_DEADZONE); if(new_controller->stick_average_x != 0.0f || new_controller->stick_average_y != 0.0f) { new_controller->is_analog = 1; } if(SDL_GetGamepadButton( controller_handles[controller_index], SDL_GAMEPAD_BUTTON_DPAD_UP)) { new_controller->stick_average_y = -1.0f; new_controller->is_analog = 0; } if(SDL_GetGamepadButton( controller_handles[controller_index], SDL_GAMEPAD_BUTTON_DPAD_DOWN)) { new_controller->stick_average_y = 1.0f; new_controller->is_analog = 0; } if(SDL_GetGamepadButton( controller_handles[controller_index], SDL_GAMEPAD_BUTTON_DPAD_LEFT)) { new_controller->stick_average_x = -1.0f; new_controller->is_analog = 0; } if(SDL_GetGamepadButton( controller_handles[controller_index], SDL_GAMEPAD_BUTTON_DPAD_RIGHT)) { new_controller->stick_average_x = 1.0f; new_controller->is_analog = 0; } threshold = 0.5f; sdl_process_input_digital_button( controller_handles[(new_controller-> stick_average_y < -threshold) ? 1 : 0], &old_controller->u.s.move_up, SDL_GAMEPAD_BUTTON_SOUTH, &new_controller->u.s.move_up); sdl_process_input_digital_button( controller_handles[(new_controller-> stick_average_y > threshold) ? 1 : 0], &old_controller->u.s.move_down, SDL_GAMEPAD_BUTTON_SOUTH, &new_controller->u.s.move_down); sdl_process_input_digital_button( controller_handles[(new_controller-> stick_average_x < -threshold) ? 1 : 0], &old_controller->u.s.move_left, SDL_GAMEPAD_BUTTON_SOUTH, &new_controller->u.s.move_left); sdl_process_input_digital_button( controller_handles[(new_controller-> stick_average_x > threshold) ? 1 : 0], &old_controller->u.s.move_right, SDL_GAMEPAD_BUTTON_SOUTH, &new_controller->u.s.move_right); sdl_process_input_digital_button( controller_handles[controller_index], &old_controller->u.s.action_down, SDL_GAMEPAD_BUTTON_SOUTH, &new_controller->u.s.action_down); sdl_process_input_digital_button( controller_handles[controller_index], &old_controller->u.s.action_right, SDL_GAMEPAD_BUTTON_EAST, &new_controller->u.s.action_right); sdl_process_input_digital_button( controller_handles[controller_index], &old_controller->u.s.action_left, SDL_GAMEPAD_BUTTON_WEST, &new_controller->u.s.action_left); sdl_process_input_digital_button( controller_handles[controller_index], &old_controller->u.s.action_up, SDL_GAMEPAD_BUTTON_NORTH, &new_controller->u.s.action_up); sdl_process_input_digital_button( controller_handles[controller_index], &old_controller->u.s.left_shoulder, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, &new_controller->u.s.left_shoulder); sdl_process_input_digital_button( controller_handles[controller_index], &old_controller->u.s.right_shoulder, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, &new_controller->u.s.right_shoulder); sdl_process_input_digital_button( controller_handles[controller_index], &old_controller->u.s.start, SDL_GAMEPAD_BUTTON_START, &new_controller->u.s.start); sdl_process_input_digital_button( controller_handles[controller_index], &old_controller->u.s.back, SDL_GAMEPAD_BUTTON_BACK, &new_controller->u.s.back); } else { /* NOTE: This controller is not plugged in. */ new_controller->is_connected = 0; } #endif } #if 0 target_queue_bytes = sound_output.LatencySampleCount * sound_output.BytesPerSample; bytes_to_write = target_queue_bytes - SDL_GetQueuedAudioSize(1); sound_buffer.SamplesPerSecond = sound_output.SamplesPerSecond; sound_buffer.SampleCount = bytes_to_write / sound_output.BytesPerSample; sound_buffer.Samples = (short *)sound_output.Samples; #endif memset(&context, 0, sizeof(context)); buffer.memory = global_backbuffer.memory; buffer.width = global_backbuffer.width; buffer.height = global_backbuffer.height; buffer.bytes_per_pixel = 4; buffer.pitch = global_backbuffer.pitch; if(game.update_and_render) { game.update_and_render(&context, &game_memory, new_input, &buffer, &sound_buffer); } /* sdl_fill_sound_buffer(&sound_output, bytes_to_write); */ work_counter = sdl_get_wall_clock(); work_seconds_elapsed = sdl_get_seconds_elapsed(last_counter, work_counter); seconds_elapsed_for_frame = work_seconds_elapsed; if(seconds_elapsed_for_frame < target_seconds_per_frame) { while(seconds_elapsed_for_frame < target_seconds_per_frame) { seconds_elapsed_for_frame = sdl_get_seconds_elapsed(last_counter, sdl_get_wall_clock()); if(target_seconds_per_frame - seconds_elapsed_for_frame >= 0.0f) { SDL_Delay((unsigned int)( (target_seconds_per_frame - seconds_elapsed_for_frame) * 1000.0)); } } } else { /* TODO: Missed frame rate! */ /* TODO: Logging. */ } end_counter = sdl_get_wall_clock(); sdl_display_buffer_in_window(&global_backbuffer, window, renderer); ms_per_frame = 1000.0f * sdl_get_seconds_elapsed(last_counter, end_counter); fps = 1000.0f / ms_per_frame; last_counter = end_counter; #if 1 if(sdl_get_seconds_elapsed(fps_last_counter, sdl_get_wall_clock()) > 0.1f) { unsigned long long EndCycleCount = __rdtsc(); unsigned long long CyclesElapsed = EndCycleCount - last_cycle_count; double MCPF = ((double)CyclesElapsed / (1000.0 * 1000.0)); /*fprintf(stderr, "%.02f ms/f, %.02f f/s, %.02f mc/f\n", ms_per_frame, fps;, MCPF);*/ static char fps_buffer[128]; snprintf(fps_buffer, sizeof(fps_buffer), "%.02f ms/f, %.02f f/s, %.02f mc/f", ms_per_frame, fps, MCPF); SDL_SetWindowTitle(window, (const char *)fps_buffer); last_cycle_count = EndCycleCount; fps_last_counter = sdl_get_wall_clock(); } #endif SWAP(struct game_input *, old_input, new_input); } } else { /* TODO: Failed to allocate Samples and Sound memory . . . */ } } else { /* TODO: This didn't work . . . */ } } else { /* TODO: This didn't work . . . */ } /* NOTE: Let the OS clean things up. */ /* NOTE: This, however, might need to be closed? Is there maybe some * clean up state that close communicates to the controller? */ /* SDLDeinitControllers(); */ /* SDL_CloseAudio(); */ /* SDL_DestroyRenderer(renderer); */ /* SDL_DestroyWindow(window); */ /* SDL_Quit(); */ return 0; }