1325 lines
46 KiB
C
1325 lines
46 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)) {
|
|
/* 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;
|
|
}
|