Files
handmadehero/sdl_handmade.c
T

1279 lines
44 KiB
C

#include "core.h"
#if OS_WINDOWS /* Remove SDL's entry point. */
#define SDL_MAIN_HANDLED
#endif
#include "handmade.h"
#include "sdl_handmade.h"
#include <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 GlobalRunning;
static unsigned long long GlobalPerfCountFrequency;
#define CONTROLLER_LEFT_THUMB_DEADZONE 8000
#define MAX_CONTROLLERS 4
SDL_GameController *ControllerHandles[MAX_CONTROLLERS];
static struct offscreen_buffer GlobalBackbuffer;
SDL_Window *LastWindow;
static int IsFullscreen;
static struct sdl_window_size WindowSize;
static struct sdl_window_position WindowPosition;
static int str_len(char *str)
{
int count = 0;
while(*str++)
count++;
return count;
}
static unsigned int SafeTruncateUInt64(unsigned long long Value)
{
unsigned int Result;
ASSERT(Value <= 0xFFFFFFFF);
Result = (unsigned int)Value;
return Result;
}
struct debug_read_file_result
DEBUGPlatformReadEntireFile(struct thread_context *Thread,
const char *Filename)
{
struct debug_read_file_result Result;
SDL_RWops *FileHandle;
memset(&Result, 0, sizeof(Result));
FileHandle = SDL_RWFromFile(Filename, "r");
if(FileHandle) {
long long FileSize;
unsigned int ObjectsRead;
FileSize = SDL_RWsize(FileHandle);
if(FileSize >= 0) {
Result.ContentsSize = SafeTruncateUInt64(FileSize);
} else {
Result.ContentsSize = 0;
SDL_RWclose(FileHandle);
return Result;
}
Result.Contents = malloc(Result.ContentsSize);
if(!Result.Contents) {
Result.ContentsSize = 0;
SDL_RWclose(FileHandle);
return Result;
}
ObjectsRead = SDL_RWread(FileHandle,
(void *)Result.Contents, Result.ContentsSize, 1);
if(ObjectsRead == 0) {
free(Result.Contents);
Result.Contents = 0;
Result.ContentsSize = 0;
SDL_RWclose(FileHandle);
return Result;
}
SDL_RWclose(FileHandle);
}
return Result;
}
int DEBUGPlatformWriteEntireFile(struct thread_context *Thread,
const char *Filename,
unsigned int MemorySize, void *Memory)
{
SDL_RWops *FileHandle = SDL_RWFromFile(Filename, "w");
if(FileHandle) {
unsigned int ObjectsWritten = SDL_RWwrite(FileHandle,
(void *)Memory, (unsigned int)MemorySize, 1);
if(ObjectsWritten == 0) {
SDL_RWclose(FileHandle);
return 0;
}
SDL_RWclose(FileHandle);
return 1;
}
return 0;
}
void DEBUGPlatformFreeFileMemory(struct thread_context *Thread, void *Memory)
{
if(Memory)
free(Memory);
}
void SetProgramIcon(struct game_offscreen_buffer *ProgramIcon)
{
SDL_Window *Window = LastWindow;
SDL_Surface *Icon =
SDL_CreateRGBSurfaceWithFormatFrom((void *)ProgramIcon->Memory,
ProgramIcon->Width, ProgramIcon->Height,
ProgramIcon->BytesPerPixel * 8,
ProgramIcon->BytesPerPixel * ProgramIcon->Width,
SDL_PIXELFORMAT_ARGB8888);
if(!Icon) {
/* TODO: This didn't work . . . SDL_GetError() */
return ;
}
SDL_SetWindowIcon(Window, Icon);
SDL_FreeSurface(Icon);
}
static struct sdl_border_size SDLGetWindowBorderSizes(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 SDLGetBorderedWindowSize(SDL_Window *Window)
{
struct sdl_border_size Border = SDLGetWindowBorderSizes(Window);
struct sdl_window_size Dimension;
SDL_GetWindowSize(Window, &Dimension.Width, &Dimension.Height);
#if OS_WINDOWS
// 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
SDLGetBorderedWindowPosition(SDL_Window *Window)
{
struct sdl_border_size Border = SDLGetWindowBorderSizes(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 SDLSetBorderedWindowPosition(SDL_Window *Window, int X, int Y)
{
struct sdl_border_size Border = SDLGetWindowBorderSizes(Window);
SDL_SetWindowPosition(Window, X, Y + Border.Top);
}
static void
SDLSetBorderedWindowSize(SDL_Window *Window, int Width, int Height)
{
struct sdl_border_size Border = SDLGetWindowBorderSizes(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
SDLResizeTexture(struct offscreen_buffer *Buffer,
SDL_Renderer *Renderer, int Width, int Height)
{
int BytesPerPixel;
if(Buffer->Texture)
SDL_DestroyTexture(Buffer->Texture);
if(Buffer->Memory)
free(Buffer->Memory);
BytesPerPixel = 4;
Buffer->Texture = SDL_CreateTexture(Renderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
Width, Height);
Buffer->Memory = malloc(Width * Height * BytesPerPixel);
Buffer->Height = Height;
Buffer->Width = Width;
Buffer->Pitch = Width * BytesPerPixel;
}
static void SDLDisplayBufferInWindow(struct offscreen_buffer *Buffer,
SDL_Window *Window,
SDL_Renderer *Renderer)
{
int OffsetX, OffsetY;
struct sdl_window_size WinSize;
OffsetX = 10;
OffsetY = 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); */
WinSize = SDLGetBorderedWindowSize(Window);
if(WinSize.Width >= Buffer->Width*2 &&
WinSize.Height >= Buffer->Height*2)
{
SDL_Rect dest_rect;
dest_rect.x = OffsetX;
dest_rect.y = OffsetY;
dest_rect.w = Buffer->Width*2;
dest_rect.h = Buffer->Height*2;
SDL_RenderCopy(Renderer, Buffer->Texture, 0,
(const SDL_Rect *)(&dest_rect));
} else {
SDL_Rect dest_rect;
dest_rect.x = OffsetX;
dest_rect.y = OffsetY;
dest_rect.w = RESOLUTION_WIDTH;
dest_rect.h = RESOLUTION_HEIGHT;
SDL_RenderCopy(Renderer, Buffer->Texture, 0,
(const SDL_Rect *)(&dest_rect));
}
SDL_RenderPresent(Renderer);
}
static void SDLInitControllers()
{
int MaxJoysticks = SDL_NumJoysticks();
int ControllerIndex = 0;
int JoystickIndex;
for(JoystickIndex = 0; JoystickIndex < MaxJoysticks; JoystickIndex++)
{
if(!SDL_IsGameController(JoystickIndex))
continue;
if(ControllerIndex >= MAX_CONTROLLERS)
break;
ControllerHandles[ControllerIndex] =
SDL_GameControllerOpen(JoystickIndex);
ControllerIndex++;
}
}
/*
static void SDLDeinitControllers()
{
for(int ControllerIndex = 0;
ControllerIndex < MAX_CONTROLLERS;
ControllerIndex++)
{
if(ControllerHandles[ControllerIndex])
SDL_GameControllerClose(ControllerHandles[ControllerIndex]);
}
}
*/
static void SDLInitSound(int SamplesPerSecond, int BufferSize)
{
SDL_AudioSpec AudioSettings;
memset(&AudioSettings, 0, sizeof(AudioSettings));
AudioSettings.freq = SamplesPerSecond;
AudioSettings.format = AUDIO_S16LSB;
AudioSettings.channels = 2;
/* TODO: Unsafe truncate from S32 to U16 */
AudioSettings.samples = (unsigned short)BufferSize;
SDL_OpenAudio(&AudioSettings, 0);
if(AudioSettings.format != AUDIO_S16LSB) {
SDL_CloseAudio();
}
}
static void
SDLFillSoundBuffer(struct sdl_sound_output *SoundOutput, int BytesToWrite)
{
SDL_QueueAudio(1, SoundOutput->Samples, BytesToWrite);
}
static void SDLClearSoundBuffer(struct sdl_sound_output *SoundOutput)
{
memset(SoundOutput->Samples, 0, SoundOutput->SecondaryBufferSize);
}
static void
SDLProcessKeyboardMessage(struct game_button_state *NewState, int IsDown)
{
if(NewState->EndedDown != IsDown) {
NewState->EndedDown = IsDown;
++NewState->HalfTransitionCount;
}
}
static void SDLProcessInputDigitalButton(SDL_GameController *Controller,
struct game_button_state *OldState,
SDL_GameControllerButton Button,
struct game_button_state *NewState)
{
NewState->EndedDown =
SDL_GameControllerGetButton(Controller, Button);
NewState->HalfTransitionCount =
(OldState->EndedDown != NewState->EndedDown) ? 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_QUIT: {
GlobalRunning = 0;
} break;
case SDL_WINDOWEVENT: {
switch(Event->window.event) {
/* TODO: We temporarily disable for ease of writing the
* rendering code. */
#if 0
/* TODO: For now we fix the width and height. */
case SDL_WINDOWEVENT_SIZE_CHANGED: {
sdl_window_dimension Dimension =
SDLGetWindowDimension(Window);
SDLResizeTexture(&GlobalBackbuffer, Renderer,
Dimension.Width, Dimension.Height);
} break;
#endif
case SDL_WINDOWEVENT_EXPOSED: {
} break;
#if 0
case SDL_WINDOWEVENT_FOCUS_GAINED: {
if(SDL_SetWindowOpacity(Window, 1.0f) != 0) {
/* TODO: This didn't work . . . SDL_GetError() */
}
} break;
case SDL_WINDOWEVENT_FOCUS_LOST: {
if(SDL_SetWindowOpacity(Window, 0.1f) != 0) {
/* TODO: This didn't work . . . SDL_GetError() */
}
} break;
#endif
}
} break;
}
}
static void SDLToggleFullscreen(SDL_Window *Window)
{
int err;
if(IsFullscreen) {
err = SDL_SetWindowFullscreen(Window, 0);
if(err == 0) {
SDLSetBorderedWindowSize(Window,
WindowSize.Width,
WindowSize.Height);
SDLSetBorderedWindowPosition(Window,
WindowPosition.X,
WindowPosition.Y);
IsFullscreen = 0;
} else {
/* TODO: This didn't work . . . SDL_GetError() */
}
} else {
WindowSize = SDLGetBorderedWindowSize(Window);
WindowPosition = SDLGetBorderedWindowPosition(Window);
err = SDL_SetWindowFullscreen(Window,
SDL_WINDOW_FULLSCREEN_DESKTOP);
if(err == 0) {
IsFullscreen = 1;
} else {
/* TODO: This didn't work . . . SDL_GetError() */
}
}
}
static void SDLProcessMessages(struct sdl_state *SDLState,
struct game_controller_input *KeyboardController)
{
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_KEYDOWN:
case SDL_KEYUP: {
SDL_Keycode KeyCode;
int IsDown, WasDown;
KeyCode = Event.key.keysym.sym;
IsDown = (Event.key.state == SDL_PRESSED);
WasDown = 0;
if(Event.key.state == SDL_RELEASED) {
WasDown = 1;
} else if(Event.key.repeat != 0) {
WasDown = 1;
}
if(Event.key.repeat == 0) {
if(KeyCode == SDLK_w) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.MoveUp, IsDown);
} else if(KeyCode == SDLK_s) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.MoveDown, IsDown);
} else if(KeyCode == SDLK_a) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.MoveLeft, IsDown);
} else if(KeyCode == SDLK_d) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.MoveRight, IsDown);
} else if(KeyCode == SDLK_q) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.LeftShoulder, IsDown);
} else if(KeyCode == SDLK_e) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.RightShoulder, IsDown);
} else if(KeyCode == SDLK_UP) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.ActionUp, IsDown);
} else if(KeyCode == SDLK_DOWN) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.ActionDown, IsDown);
} else if(KeyCode == SDLK_LEFT) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.ActionLeft, IsDown);
} else if(KeyCode == SDLK_RIGHT) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.ActionRight, IsDown);
} else if(KeyCode == SDLK_ESCAPE) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.Back, IsDown);
} else if(KeyCode == SDLK_SPACE) {
SDLProcessKeyboardMessage(
&KeyboardController->u.s.Start, IsDown);
}
}
if(WasDown) {
/* 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 AltKeyWasDown = (Event.key.keysym.mod & KMOD_ALT);
if(KeyCode == SDLK_F4 && AltKeyWasDown)
GlobalRunning = 0;
#if BUILD_INTERNAL
if(KeyCode == SDLK_ESCAPE)
GlobalRunning = 0;
#endif
if((KeyCode == SDLK_RETURN) && AltKeyWasDown) {
SDLToggleFullscreen(Window);
}
}
} break;
default: {
HandleEvent(&Event);
} break;
}
}
}
static float SDLProcessInputStickValue(float Value, int DeadZoneThreshold)
{
float Result = 0.0f;
if(Value < -(float)DeadZoneThreshold)
Result = (Value + (float)DeadZoneThreshold) /
(32768.0f - (float)DeadZoneThreshold);
else if(Value > (float)DeadZoneThreshold)
Result = (Value + (float)DeadZoneThreshold) /
(32767.0f - (float)DeadZoneThreshold);
return Result;
}
#define DEFAULT_REFRESH_RATE 60
static int SDLGetWindowRefreshRate(SDL_Window *Window)
{
int DisplayIndex = SDL_GetWindowDisplayIndex(Window);
SDL_DisplayMode Mode;
if(SDL_GetDesktopDisplayMode(DisplayIndex, &Mode) != 0)
return DEFAULT_REFRESH_RATE;
if(Mode.refresh_rate == 0)
return DEFAULT_REFRESH_RATE;
return Mode.refresh_rate;
}
static unsigned long long SDLGetWallClock()
{
return SDL_GetPerformanceCounter();
}
static float SDLGetSecondsElapsed(unsigned long long Start,
unsigned long long End)
{
return (float)(End - Start) / (float)GlobalPerfCountFrequency;
}
struct sdl_game_code {
void *GameCodeDLL;
unsigned int DLLLastWriteTime;
void (*UpdateAndRender)(struct thread_context *Thread,
struct game_memory *,
struct game_input *,
struct game_offscreen_buffer *,
struct game_sound_output_buffer *);
int IsValid;
};
static unsigned int GetLastWriteTime(const char *FileName)
{
struct stat file_info;
memset(&file_info, 0, sizeof(file_info));
if(stat(FileName, &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"
#else
#error Unknown OS: please specify GAME_DLL_NAME
#endif
static struct sdl_game_code SDLLoadGameCode(char *DLLPath)
{
struct sdl_game_code Result;
memset(&Result, 0, sizeof(Result));
#if OS_MACOS || OS_LINUX
Result.GameCodeDLL = dlopen(DLLPath, RTLD_NOW);
#elif OS_WINDOWS
Result.GameCodeDLL = LoadLibraryA(DLLPath);
#else
#error Unknown OS: implement DLL loading...
#endif
if(Result.GameCodeDLL) {
Result.DLLLastWriteTime = GetLastWriteTime(DLLPath);
#if OS_MACOS || OS_LINUX
Result.UpdateAndRender = (void(*)(struct thread_context *,
struct game_memory *,
struct game_input *, struct game_offscreen_buffer *,
struct game_sound_output_buffer *))dlsym(Result.GameCodeDLL,
"UpdateAndRender");
#elif OS_WINDOWS
Result.UpdateAndRender = (void(*)(thread_context *, game_memory *,
game_input *, game_offscreen_buffer *,
game_sound_output_buffer *))GetProcAddress(Result.GameCodeDLL,
"UpdateAndRender");
#else
#error Unknown OS: implement DLL loading...
#endif
if(!Result.UpdateAndRender) {
/* TODO: Diagnostic. dlerror() */
}
Result.IsValid = Result.UpdateAndRender ? 1 : 0;
} else {
/* TODO: Diagnostic. dlerror() */
}
if(!Result.IsValid) {
Result.UpdateAndRender = 0;
}
return Result;
}
static void SDLUnloadGameCode(struct sdl_game_code *GameCode)
{
if(GameCode->GameCodeDLL) {
#if OS_MACOS || OS_LINUX
dlclose(GameCode->GameCodeDLL); /* NOTE: Might fail. */
#elif OS_WINDOWS
FreeLibrary(GameCode->GameCodeDLL);
#else
#error Unknown OS: implement closing of DLL
#endif
}
GameCode->IsValid = 0;
GameCode->UpdateAndRender = 0;
}
void GetExecutablePath(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...
#else
#error Unknown OS: Implement getting executable location.
#endif
}
}
static void SDLGetEXEDirPath(char *path, size_t path_size)
{
#if OS_MACOS || OS_LINUX
char delimeter = '/';
#elif OS_WINDOWS
char delimeter = '\\';
#else
#error Unknown OS: OS uses forward or backward slashes for paths?
#endif
unsigned int len, i;
GetExecutablePath(path, path_size);
len = str_len(path);
i = len;
while(path[i] != delimeter || i == 0) {
i--;
}
path[i+1] = '\0';
}
static void SDLGetDLLPath(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 SDLState;
SDL_Window *Window;
memset(&SDLState, 0, sizeof(SDLState));
SDLGetEXEDirPath(SDLState.EXEDirPath, sizeof(SDLState.EXEDirPath));
SDLGetDLLPath(SDLState.DLLPath, sizeof(SDLState.DLLPath),
SDLState.EXEDirPath);
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER)) {
/* TODO: This didn't work . . . */
}
SDLInitControllers();
/* 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",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
RESOLUTION_WIDTH+20, RESOLUTION_HEIGHT+20,
SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE/* |
SDL_WINDOW_ALWAYS_ON_TOP*/);
if(Window) {
SDL_DisplayMode display_mode;
SDL_Renderer *Renderer;
LastWindow = Window;
SDL_GetCurrentDisplayMode(0, &display_mode);
Renderer = SDL_CreateRenderer(Window, -1, SDL_RENDERER_PRESENTVSYNC);
/* 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 TempContext;
SDL_Cursor *PointerCursor;
SDL_Rect UsableDisplayRect;
struct sdl_window_size CurrentWindowSize;
struct sdl_sound_output SoundOutput;
#if BUILD_DEBUG
void *BaseAddress = (void *)TB(2);
#else
void *BaseAddress = (void *)(0);
#endif
struct game_memory GameMemory;
memset(&TempContext, 0, sizeof(TempContext));
/* TODO: Make it a global. */
PointerCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
SDL_SetCursor(PointerCursor);
#if !BUILD_INTERNAL
if(SDL_ShowCursor(SDL_DISABLE) < 0) {
/* TODO: This didn't work . . . SDL_GetError() */
}
#endif
SDL_GetDisplayUsableBounds(0, &UsableDisplayRect);
CurrentWindowSize = SDLGetBorderedWindowSize(Window);
SDLSetBorderedWindowPosition(Window,
(UsableDisplayRect.x + UsableDisplayRect.w -
CurrentWindowSize.Width),
(UsableDisplayRect.y + UsableDisplayRect.h -
CurrentWindowSize.Height));
GlobalRunning = 1;
/* sdl_window_dimension Dimension =
SDLGetWindowDimension(Window); */
/* SDLResizeTexture(&GlobalBackbuffer, Renderer,
Dimension.Width, Dimension.Height); */
SDLResizeTexture(&GlobalBackbuffer, Renderer,
RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
memset(&SoundOutput, 0, sizeof(SoundOutput));
SoundOutput.SamplesPerSecond = 48000;
SoundOutput.BytesPerSample = sizeof(short) * 2;
SoundOutput.SecondaryBufferSize =
SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample;
SoundOutput.LatencySampleCount =
SoundOutput.SamplesPerSecond / 15;
SDLInitSound(SoundOutput.SamplesPerSecond,
SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample /
60);
/* TODO: Should be paused until we have sound to play. */
SDL_PauseAudio(0);
SoundOutput.Samples = calloc(SoundOutput.SamplesPerSecond,
SoundOutput.BytesPerSample);
/*SoundOutput.Samples = malloc(SoundOutput.SecondaryBufferSize);*/
/* NOTE: calloc auto clears to zero */
/* SDLClearSoundBuffer(&SoundOutput); */
memset(&GameMemory, 0, sizeof(GameMemory));
GameMemory.PermanentStorageSize = MB(64);
GameMemory.TransientStorageSize = GB(1);
GameMemory.DEBUGPlatformReadEntireFile =
&DEBUGPlatformReadEntireFile;
GameMemory.SetProgramIcon = &SetProgramIcon;
/* GameMemory.DEBUGPlatformWriteEntireFile =
&DEBUGPlatformWriteEntireFile; */
GameMemory.DEBUGPlatformFreeFileMemory =
&DEBUGPlatformFreeFileMemory;
SDLState.TotalSize = GameMemory.PermanentStorageSize +
GameMemory.TransientStorageSize;
#if OS_MACOS || OS_LINUX
/* NOTE: On MacOS and Linux, mmap seems to zero-fill anonymous
* memory as a side-effect of security. */
SDLState.GameMmemoryBlock = mmap(BaseAddress,
(size_t)SDLState.TotalSize,
PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
#elif OS_WINDOWS
SDLState.GameMmemoryBlock = VirtualAlloc(BaseAddress,
SDLState.TotalSize,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
#else
#error Unknown OS: please, allocate memory for arena
#endif
GameMemory.PermanentStorage = SDLState.GameMmemoryBlock;
GameMemory.TransientStorage =
(unsigned char *)(GameMemory.PermanentStorage) +
GameMemory.PermanentStorageSize;
if(SoundOutput.Samples &&
GameMemory.PermanentStorage &&
GameMemory.TransientStorage)
{
struct game_input Input[2];
unsigned long long LastCounter;
unsigned long long LastCycleCount;
int MonitorRefreshHz, GameUpdateHz;
float TargetSecondsPerFrame;
struct game_input *NewInput, *OldInput;
struct sdl_game_code Game;
unsigned long long FPSLastCounter;
MonitorRefreshHz = SDLGetWindowRefreshRate(Window);
/*GameUpdateHz = MonitorRefreshHz;*/
GameUpdateHz = 30; /* NOTE: Temporarily target 30 FPS. */
TargetSecondsPerFrame = 1.0f / (float)GameUpdateHz;
NewInput = &Input[0];
OldInput = &Input[1];
memset(Input, 0, sizeof(Input));
GlobalPerfCountFrequency = SDL_GetPerformanceFrequency();
Game = SDLLoadGameCode(SDLState.DLLPath);
SDL_ShowWindow(Window); // Everything is ready; display the window.
FPSLastCounter = SDLGetWallClock();
while(GlobalRunning) {
unsigned int NewDLLWriteTime;
int MouseX, MouseY;
int WindowX, WindowY;
unsigned int SDLMouseButtons;
struct game_controller_input *OldKeyboardController,
*NewKeyboardController;
struct game_controller_input ZeroController;
unsigned int ButtonIndex;
unsigned int ControllerIndex;
int TargetQueueBytes;
int BytesToWrite;
struct game_sound_output_buffer SoundBuffer;
struct thread_context Context;
struct game_offscreen_buffer Buffer;
unsigned long long WorkCounter;
float WorkSecondsElapsed;
float SecondsElapsedForFrame;
unsigned long long EndCounter;
float MSPerFrame;
float FPS;
NewInput->dtForFrame = TargetSecondsPerFrame;
LastCounter = SDLGetWallClock();
/* 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. */
LastCycleCount = __rdtsc();
NewDLLWriteTime = GetLastWriteTime(SDLState.DLLPath);
if(NewDLLWriteTime > Game.DLLLastWriteTime) {
SDLUnloadGameCode(&Game);
Game = SDLLoadGameCode(SDLState.DLLPath);
}
SDL_GetGlobalMouseState(&MouseX, &MouseY);
SDL_GetWindowPosition(Window, &WindowX, &WindowY);
NewInput->MouseX = MouseX - WindowX;
NewInput->MouseY = MouseY - WindowY;
SDLMouseButtons = SDL_GetMouseState(NULL, NULL);
NewInput->MouseZ = 0; /* TODO: Support mouse wheel? */
SDLProcessKeyboardMessage(&(NewInput->MouseButtons[0]),
SDL_BUTTON_LMASK & SDLMouseButtons);
SDLProcessKeyboardMessage(&(NewInput->MouseButtons[1]),
SDL_BUTTON_MMASK & SDLMouseButtons);
SDLProcessKeyboardMessage(&(NewInput->MouseButtons[2]),
SDL_BUTTON_RMASK & SDLMouseButtons);
/* WARNING: TODO: SDL_BUTTON_X1MASK and SDL_BUTTON_X2MASK
* cannot be on at the same time for some reason. */
SDLProcessKeyboardMessage(&(NewInput->MouseButtons[3]),
SDL_BUTTON_X1MASK & SDLMouseButtons);
SDLProcessKeyboardMessage(&(NewInput->MouseButtons[4]),
SDL_BUTTON_X2MASK & SDLMouseButtons);
OldKeyboardController = GetController(OldInput, 0);
NewKeyboardController = GetController(NewInput, 0);
memset(&ZeroController, 0, sizeof(ZeroController));
*NewKeyboardController = ZeroController;
NewKeyboardController->IsConnected = 1;
for(ButtonIndex = 0;
ButtonIndex <
ARRAY_SIZE(NewKeyboardController->u.Buttons);
ButtonIndex++)
{
NewKeyboardController->u.
Buttons[ButtonIndex].EndedDown =
OldKeyboardController->u.
Buttons[ButtonIndex].EndedDown;
}
SDLProcessMessages(&SDLState, NewKeyboardController);
for(ControllerIndex = 0;
ControllerIndex < MAX_CONTROLLERS;
ControllerIndex++)
{
struct game_controller_input *OldController,
*NewController;
OldController =
GetController(OldInput, ControllerIndex+1);
NewController =
GetController(NewInput, ControllerIndex+1);
if(ControllerHandles[ControllerIndex] != 0 &&
SDL_GameControllerGetAttached(
ControllerHandles[ControllerIndex]))
{
short StickX, StickY;
float Threshold;
NewController->IsAnalog =
OldController->IsAnalog;
NewController->IsConnected = 1;
StickX = SDL_GameControllerGetAxis(
ControllerHandles[ControllerIndex],
SDL_CONTROLLER_AXIS_LEFTX);
StickY = SDL_GameControllerGetAxis(
ControllerHandles[ControllerIndex],
SDL_CONTROLLER_AXIS_LEFTY);
NewController->IsAnalog = 1;
NewController->StickAverageX =
SDLProcessInputStickValue((float)StickX,
CONTROLLER_LEFT_THUMB_DEADZONE);
NewController->StickAverageY =
SDLProcessInputStickValue((float)StickY,
CONTROLLER_LEFT_THUMB_DEADZONE);
if(NewController->StickAverageX != 0.0f ||
NewController->StickAverageY != 0.0f)
{
NewController->IsAnalog = 1;
}
if(SDL_GameControllerGetButton(
ControllerHandles[ControllerIndex],
SDL_CONTROLLER_BUTTON_DPAD_UP))
{
NewController->StickAverageY = -1.0f;
NewController->IsAnalog = 0;
}
if(SDL_GameControllerGetButton(
ControllerHandles[ControllerIndex],
SDL_CONTROLLER_BUTTON_DPAD_DOWN))
{
NewController->StickAverageY = 1.0f;
NewController->IsAnalog = 0;
}
if(SDL_GameControllerGetButton(
ControllerHandles[ControllerIndex],
SDL_CONTROLLER_BUTTON_DPAD_LEFT))
{
NewController->StickAverageX = -1.0f;
NewController->IsAnalog = 0;
}
if(SDL_GameControllerGetButton(
ControllerHandles[ControllerIndex],
SDL_CONTROLLER_BUTTON_DPAD_RIGHT))
{
NewController->StickAverageX = 1.0f;
NewController->IsAnalog = 0;
}
Threshold = 0.5f;
SDLProcessInputDigitalButton(
ControllerHandles[(NewController->
StickAverageY < -Threshold) ? 1 : 0],
&OldController->u.s.MoveUp,
SDL_CONTROLLER_BUTTON_A,
&NewController->u.s.MoveUp);
SDLProcessInputDigitalButton(
ControllerHandles[(NewController->
StickAverageY > Threshold) ? 1 : 0],
&OldController->u.s.MoveDown,
SDL_CONTROLLER_BUTTON_A,
&NewController->u.s.MoveDown);
SDLProcessInputDigitalButton(
ControllerHandles[(NewController->
StickAverageX < -Threshold) ? 1 : 0],
&OldController->u.s.MoveLeft,
SDL_CONTROLLER_BUTTON_A,
&NewController->u.s.MoveLeft);
SDLProcessInputDigitalButton(
ControllerHandles[(NewController->
StickAverageX > Threshold) ? 1 : 0],
&OldController->u.s.MoveRight,
SDL_CONTROLLER_BUTTON_A,
&NewController->u.s.MoveRight);
SDLProcessInputDigitalButton(
ControllerHandles[ControllerIndex],
&OldController->u.s.ActionDown,
SDL_CONTROLLER_BUTTON_A,
&NewController->u.s.ActionDown);
SDLProcessInputDigitalButton(
ControllerHandles[ControllerIndex],
&OldController->u.s.ActionRight,
SDL_CONTROLLER_BUTTON_B,
&NewController->u.s.ActionRight);
SDLProcessInputDigitalButton(
ControllerHandles[ControllerIndex],
&OldController->u.s.ActionLeft,
SDL_CONTROLLER_BUTTON_X,
&NewController->u.s.ActionLeft);
SDLProcessInputDigitalButton(
ControllerHandles[ControllerIndex],
&OldController->u.s.ActionUp,
SDL_CONTROLLER_BUTTON_Y,
&NewController->u.s.ActionUp);
SDLProcessInputDigitalButton(
ControllerHandles[ControllerIndex],
&OldController->u.s.LeftShoulder,
SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
&NewController->u.s.LeftShoulder);
SDLProcessInputDigitalButton(
ControllerHandles[ControllerIndex],
&OldController->u.s.RightShoulder,
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
&NewController->u.s.RightShoulder);
SDLProcessInputDigitalButton(
ControllerHandles[ControllerIndex],
&OldController->u.s.Start,
SDL_CONTROLLER_BUTTON_START,
&NewController->u.s.Start);
SDLProcessInputDigitalButton(
ControllerHandles[ControllerIndex],
&OldController->u.s.Back,
SDL_CONTROLLER_BUTTON_BACK,
&NewController->u.s.Back);
} else {
/* NOTE: This controller is not plugged in. */
NewController->IsConnected = 0;
}
}
TargetQueueBytes = SoundOutput.LatencySampleCount *
SoundOutput.BytesPerSample;
BytesToWrite =
TargetQueueBytes - SDL_GetQueuedAudioSize(1);
SoundBuffer.SamplesPerSecond =
SoundOutput.SamplesPerSecond;
SoundBuffer.SampleCount =
BytesToWrite / SoundOutput.BytesPerSample;
SoundBuffer.Samples = (short *)SoundOutput.Samples;
memset(&Context, 0, sizeof(Context));
Buffer.Memory = GlobalBackbuffer.Memory;
Buffer.Width = GlobalBackbuffer.Width;
Buffer.Height = GlobalBackbuffer.Height;
Buffer.BytesPerPixel = 4;
Buffer.Pitch = GlobalBackbuffer.Pitch;
if(Game.UpdateAndRender) {
Game.UpdateAndRender(&Context, &GameMemory,
NewInput, &Buffer,
&SoundBuffer);
}
SDLFillSoundBuffer(&SoundOutput, BytesToWrite);
WorkCounter = SDLGetWallClock();
WorkSecondsElapsed =
SDLGetSecondsElapsed(LastCounter, WorkCounter);
SecondsElapsedForFrame = WorkSecondsElapsed;
if(SecondsElapsedForFrame < TargetSecondsPerFrame) {
while(SecondsElapsedForFrame < TargetSecondsPerFrame)
{
SecondsElapsedForFrame =
SDLGetSecondsElapsed(LastCounter,
SDLGetWallClock());
if(TargetSecondsPerFrame -
SecondsElapsedForFrame >= 0.0f)
{
SDL_Delay((unsigned int)(
(TargetSecondsPerFrame -
SecondsElapsedForFrame) * 1000.0));
}
}
} else {
/* TODO: Missed frame rate! */
/* TODO: Logging. */
}
EndCounter = SDLGetWallClock();
SDLDisplayBufferInWindow(&GlobalBackbuffer, Window,
Renderer);
MSPerFrame =
1000.0f *
SDLGetSecondsElapsed(LastCounter, EndCounter);
FPS = 1000.0f / MSPerFrame;
LastCounter = EndCounter;
#if 1
if(SDLGetSecondsElapsed(FPSLastCounter,
SDLGetWallClock()) > 0.1f)
{
unsigned long long EndCycleCount = __rdtsc();
unsigned long long CyclesElapsed =
EndCycleCount - LastCycleCount;
double MCPF =
((double)CyclesElapsed / (1000.0 * 1000.0));
/*fprintf(stderr,
"%.02f ms/f, %.02f f/s, %.02f mc/f\n",
MSPerFrame, FPS, MCPF);*/
static char fps_buffer[128];
snprintf(fps_buffer, sizeof(fps_buffer),
"%.02f ms/f, %.02f f/s, %.02f mc/f",
MSPerFrame, FPS, MCPF);
SDL_SetWindowTitle(Window,
(const char *)fps_buffer);
LastCycleCount = EndCycleCount;
FPSLastCounter = SDLGetWallClock();
}
#endif
SWAP(struct game_input *, OldInput, NewInput);
}
} 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;
}