Files
handmadehero/sdl_handmade.c
T

1312 lines
45 KiB
C

/* 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 <SDL3/SDL.h>
#include <stdlib.h> /* malloc, calloc */
#include <string.h> /* memset, memcpy */
#include <stdio.h>
#include <sys/stat.h> /* fstat */
#include <fcntl.h> /* open */
#include <sys/stat.h> /* stat */
#if OS_MACOS || OS_LINUX
#include <dlfcn.h> /* dlopen */
#include <sys/mman.h> /* mmap */
#include <unistd.h> /* close, write, read, readlink, unlink */
#endif
#if OS_WINDOWS
#include <windows.h> /* VirtualAlloc */
#endif
#if OS_MACOS
#include <mach-o/dyld.h> /* _NSGetExecutablePath */
#endif
/* NOTE: This is so it compiles on ARM. */
#if ARCHITECTURE_X64 || ARCHITECTURE_X86
#if OS_MACOS || OS_LINUX
#include <x86intrin.h>
#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
int main(int argc, char *argv[])
{
struct sdl_state sdl_state;
SDL_Window *window;
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 | SDL_INIT_AUDIO | 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
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);
SDL_ShowWindow(window); // Everything is ready; display the 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;
}