1212 lines
43 KiB
C
1212 lines
43 KiB
C
/* NOTE: This layer only supports MacOS and Linux, for now. */
|
|
|
|
#include "core.h"
|
|
|
|
#include "handmade.h"
|
|
#include "sdl_handmade.h"
|
|
|
|
#include <SDL.h>
|
|
#include <sys/mman.h> /* mmap */
|
|
#include <stdlib.h> /* malloc, calloc */
|
|
#include <string.h> /* memset, memcpy */
|
|
#include <stdio.h>
|
|
|
|
#include <sys/stat.h> /* fstat */
|
|
#include <fcntl.h> /* open */
|
|
#include <unistd.h> /* close, write, read, readlink, unlink */
|
|
#include <dlfcn.h> /* dlopen */
|
|
#include <sys/stat.h> /* stat */
|
|
#if __APPLE__ && __MACH__
|
|
#include <mach-o/dyld.h> /* _NSGetExecutablePath */
|
|
#endif
|
|
|
|
/* NOTE: This is so it compiles on ARM. */
|
|
#if __x86_64__ || __i386__
|
|
#include <x86intrin.h>
|
|
#endif
|
|
|
|
#include "program_icon.c"
|
|
|
|
#define RESOLUTION_WIDTH 960
|
|
#define RESOLUTION_HEIGHT 540
|
|
|
|
static B32 GlobalRunning;
|
|
|
|
static U64 GlobalPerfCountFrequency;
|
|
|
|
#define CONTROLLER_LEFT_THUMB_DEADZONE 8000
|
|
#define MAX_CONTROLLERS 4
|
|
SDL_GameController *ControllerHandles[MAX_CONTROLLERS];
|
|
|
|
static offscreen_buffer GlobalBackbuffer;
|
|
|
|
static S32 str_len(char *str)
|
|
{
|
|
S32 count = 0;
|
|
while(*str++)
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
static U32 SafeTruncateUInt64(U64 Value)
|
|
{
|
|
U32 Result;
|
|
|
|
ASSERT(Value <= 0xFFFFFFFF);
|
|
Result = (U32)Value;
|
|
return Result;
|
|
}
|
|
|
|
debug_read_file_result DEBUGPlatformReadEntireFile(thread_context *Thread,
|
|
const char *Filename)
|
|
{
|
|
debug_read_file_result Result;
|
|
S32 FileHandle;
|
|
struct stat FileStatus;
|
|
U32 BytesToRead;
|
|
U8 *NextByteLocation;
|
|
|
|
memset(&Result, 0, sizeof(Result));
|
|
FileHandle = open(Filename, O_RDONLY);
|
|
if(FileHandle == -1) {
|
|
return Result;
|
|
}
|
|
|
|
if(fstat(FileHandle, &FileStatus) == -1) {
|
|
close(FileHandle);
|
|
return Result;
|
|
}
|
|
Result.ContentsSize = SafeTruncateUInt64(FileStatus.st_size);
|
|
|
|
Result.Contents = malloc(Result.ContentsSize);
|
|
if(!Result.Contents) {
|
|
Result.ContentsSize = 0;
|
|
close(FileHandle);
|
|
return Result;
|
|
}
|
|
|
|
BytesToRead = Result.ContentsSize;
|
|
NextByteLocation = (U8 *)Result.Contents;
|
|
while(BytesToRead) {
|
|
U32 BytesRead;
|
|
|
|
BytesRead = read(FileHandle, NextByteLocation, BytesToRead);
|
|
if(BytesRead == (U32)-1) {
|
|
free(Result.Contents);
|
|
Result.Contents = 0;
|
|
Result.ContentsSize = 0;
|
|
close(FileHandle);
|
|
return Result;
|
|
}
|
|
BytesToRead -= BytesRead;
|
|
NextByteLocation += BytesRead;
|
|
}
|
|
|
|
close(FileHandle);
|
|
|
|
return Result;
|
|
}
|
|
|
|
B32 DEBUGPlatformWriteEntireFile(thread_context *Thread,
|
|
const char *Filename, U32 MemorySize,
|
|
void *Memory)
|
|
{
|
|
S32 FileHandle;
|
|
U32 BytesToWrite;
|
|
U8 *NextByteLocation;
|
|
|
|
FileHandle = open(Filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR |
|
|
S_IRGRP | S_IROTH);
|
|
if(FileHandle == -1)
|
|
return FALSE;
|
|
|
|
BytesToWrite = MemorySize;
|
|
NextByteLocation = (U8*)Memory;
|
|
while(BytesToWrite) {
|
|
U32 BytesWritten;
|
|
|
|
BytesWritten = write(FileHandle, NextByteLocation, BytesToWrite);
|
|
if(BytesWritten == (U32)-1) {
|
|
close(FileHandle);
|
|
return FALSE;
|
|
}
|
|
BytesToWrite -= BytesWritten;
|
|
NextByteLocation += BytesWritten;
|
|
}
|
|
|
|
close(FileHandle);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void DEBUGPlatformFreeFileMemory(thread_context *Thread, void *Memory)
|
|
{
|
|
if(Memory)
|
|
free(Memory);
|
|
}
|
|
|
|
static sdl_window_dimension SDLGetWindowDimension(SDL_Window *Window)
|
|
{
|
|
sdl_window_dimension Dimension;
|
|
SDL_GetWindowSize(Window, &Dimension.Width, &Dimension.Height);
|
|
return Dimension;
|
|
}
|
|
|
|
static void SDLResizeTexture(offscreen_buffer *Buffer,
|
|
SDL_Renderer *Renderer, int Width, int Height)
|
|
{
|
|
S32 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(offscreen_buffer Buffer,
|
|
SDL_Window *Window,
|
|
SDL_Renderer *Renderer)
|
|
{
|
|
SDL_Rect dest_rect;
|
|
S32 OffsetX = 10;
|
|
S32 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); */
|
|
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()
|
|
{
|
|
S32 MaxJoysticks, ControllerIndex, JoystickIndex;
|
|
|
|
MaxJoysticks = SDL_NumJoysticks();
|
|
ControllerIndex = 0;
|
|
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()
|
|
{
|
|
S32 ControllerIndex;
|
|
|
|
for(ControllerIndex = 0;
|
|
ControllerIndex < MAX_CONTROLLERS;
|
|
ControllerIndex++)
|
|
{
|
|
if(ControllerHandles[ControllerIndex])
|
|
SDL_GameControllerClose(ControllerHandles[ControllerIndex]);
|
|
}
|
|
}
|
|
*/
|
|
|
|
static void SDLInitSound(S32 SamplesPerSecond, S32 BufferSize)
|
|
{
|
|
SDL_AudioSpec AudioSettings;
|
|
|
|
memset(&AudioSettings, 0, sizeof(AudioSettings));
|
|
|
|
AudioSettings.freq = SamplesPerSecond;
|
|
AudioSettings.format = AUDIO_S16LSB;
|
|
AudioSettings.channels = 2;
|
|
AudioSettings.samples = BufferSize;
|
|
|
|
SDL_OpenAudio(&AudioSettings, 0);
|
|
|
|
if(AudioSettings.format != AUDIO_S16LSB) {
|
|
SDL_CloseAudio();
|
|
}
|
|
}
|
|
|
|
static void SDLFillSoundBuffer(sdl_sound_output *SoundOutput,
|
|
S32 BytesToWrite)
|
|
{
|
|
SDL_QueueAudio(1, SoundOutput->Samples, BytesToWrite);
|
|
}
|
|
|
|
static void SDLClearSoundBuffer(sdl_sound_output *SoundOutput)
|
|
{
|
|
memset(SoundOutput->Samples, 0, SoundOutput->SecondaryBufferSize);
|
|
}
|
|
|
|
static void SDLProcessKeyboardMessage(game_button_state *NewState,
|
|
B32 IsDown)
|
|
{
|
|
if(NewState->EndedDown != IsDown) {
|
|
NewState->EndedDown = IsDown;
|
|
++NewState->HalfTransitionCount;
|
|
}
|
|
}
|
|
|
|
static void SDLProcessInputDigitalButton(SDL_GameController *Controller,
|
|
game_button_state *OldState,
|
|
SDL_GameControllerButton Button,
|
|
game_button_state *NewState)
|
|
{
|
|
NewState->EndedDown = SDL_GameControllerGetButton(Controller, Button);
|
|
NewState->HalfTransitionCount =
|
|
(OldState->EndedDown != NewState->EndedDown) ? 1 : 0;
|
|
}
|
|
|
|
static void HandleEvent(SDL_Event *Event)
|
|
{
|
|
/* TODO: Should this be below scopes? */
|
|
SDL_Window *Window;
|
|
SDL_Renderer *Renderer;
|
|
|
|
Window = SDL_GetWindowFromID(Event->window.windowID);
|
|
Renderer = SDL_GetRenderer(Window);
|
|
|
|
switch(Event->type) {
|
|
case SDL_QUIT: {
|
|
GlobalRunning = FALSE;
|
|
} break;
|
|
|
|
case SDL_WINDOWEVENT: {
|
|
switch(Event->window.event) {
|
|
/* TODO: We temporarily disable for ease of writing the
|
|
* rendering code. */
|
|
#if 0
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED: {
|
|
sdl_window_dimension Dimension;
|
|
|
|
Dimension = SDLGetWindowDimension(Window);
|
|
/* TODO: For now we fix the width and height. */
|
|
SDLResizeTexture(&GlobalBackbuffer, Renderer,
|
|
Dimension.Width, Dimension.Height);
|
|
} break;
|
|
#endif
|
|
|
|
case SDL_WINDOWEVENT_EXPOSED: {
|
|
} break;
|
|
|
|
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.3f) != 0) {
|
|
/* TODO: This didn't work . . . SDL_GetError() */
|
|
}
|
|
} break;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
static void SDLGetInputFileLocation(sdl_state *SDLState, char *path,
|
|
size_t path_size, S32 SlotIndex)
|
|
{
|
|
const char *file_name = "loop.hmi";
|
|
|
|
ASSERT(SlotIndex == 1);
|
|
|
|
snprintf(path, path_size, "%s%s", SDLState->EXEDirPath, file_name);
|
|
}
|
|
|
|
sdl_replay_buffer *SDLGetReplayBuffer(sdl_state *SDLState,
|
|
S32 Index)
|
|
{
|
|
ASSERT(Index < (S32)ARRAY_SIZE(SDLState->ReplayBuffers));
|
|
return &(SDLState->ReplayBuffers[Index]);
|
|
}
|
|
|
|
static void SDLBeginRecordingInput(sdl_state *SDLState,
|
|
S32 InputRecordingIndex)
|
|
{
|
|
sdl_replay_buffer *ReplayBuffer =
|
|
SDLGetReplayBuffer(SDLState, InputRecordingIndex);
|
|
if(ReplayBuffer->MemoryBlock) {
|
|
char FileName[SDL_STATE_PATH_SIZE];
|
|
|
|
SDLGetInputFileLocation(SDLState, FileName, sizeof(FileName),
|
|
InputRecordingIndex);
|
|
|
|
SDLState->InputRecordingIndex = InputRecordingIndex;
|
|
|
|
if(unlink(FileName) != 0) {
|
|
/* TODO: Diagnostics . . . */
|
|
}
|
|
|
|
SDLState->RecordingHandle =
|
|
open(FileName, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR |
|
|
S_IRGRP | S_IROTH);
|
|
/* write(SDLState->RecordingHandle, SDLState->GameMmemoryBlock,
|
|
(size_t)(SDLState->TotalSize)); */
|
|
memcpy(ReplayBuffer->MemoryBlock, SDLState->GameMmemoryBlock,
|
|
(size_t)(SDLState->TotalSize));
|
|
}
|
|
}
|
|
|
|
static void SDLEndRecordingInput(sdl_state *SDLState)
|
|
{
|
|
close(SDLState->RecordingHandle);
|
|
SDLState->InputRecordingIndex = 0;
|
|
}
|
|
|
|
static void SDLBeginInputPlayback(sdl_state *SDLState,
|
|
S32 InputPlayingIndex)
|
|
{
|
|
sdl_replay_buffer *ReplayBuffer =
|
|
SDLGetReplayBuffer(SDLState, InputPlayingIndex);
|
|
if(ReplayBuffer->MemoryBlock) {
|
|
char FileName[SDL_STATE_PATH_SIZE];
|
|
ssize_t size;
|
|
|
|
SDLGetInputFileLocation(SDLState, FileName, sizeof(FileName),
|
|
InputPlayingIndex);
|
|
|
|
SDLState->InputPlayingIndex = InputPlayingIndex;
|
|
SDLState->PlaybackHandle =
|
|
open(FileName, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR |
|
|
S_IRGRP | S_IROTH);
|
|
/* size = read(SDLState->PlaybackHandle, SDLState->GameMmemoryBlock,
|
|
(size_t)(SDLState->TotalSize)); */
|
|
memcpy(SDLState->GameMmemoryBlock, ReplayBuffer->MemoryBlock,
|
|
(size_t)(SDLState->TotalSize));
|
|
}
|
|
}
|
|
|
|
static void SDLEndInputPlayback(sdl_state *SDLState)
|
|
{
|
|
close(SDLState->PlaybackHandle);
|
|
SDLState->InputPlayingIndex = 0;
|
|
}
|
|
|
|
static void SDLRecordInput(sdl_state *SDLState, game_input *NewInput)
|
|
{
|
|
write(SDLState->RecordingHandle, NewInput, sizeof(*NewInput));
|
|
}
|
|
|
|
static void SDLPlaybackInput(sdl_state *SDLState, game_input *NewInput)
|
|
{
|
|
ssize_t size;
|
|
|
|
size = read(SDLState->PlaybackHandle, NewInput, sizeof(*NewInput));
|
|
if(size == 0 || size == -1) {
|
|
S32 PlayingIndex;
|
|
|
|
PlayingIndex = SDLState->InputPlayingIndex;
|
|
SDLEndInputPlayback(SDLState);
|
|
SDLBeginInputPlayback(SDLState, PlayingIndex);
|
|
size = read(SDLState->PlaybackHandle, NewInput, sizeof(*NewInput));
|
|
}
|
|
}
|
|
|
|
static void SDLProcessMessages(sdl_state *SDLState,
|
|
game_controller_input *KeyboardController)
|
|
{
|
|
SDL_Event Event;
|
|
while(SDL_PollEvent(&Event)) {
|
|
switch(Event.type) {
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP: {
|
|
SDL_Keycode KeyCode;
|
|
B32 IsDown, WasDown;
|
|
|
|
KeyCode = Event.key.keysym.sym;
|
|
IsDown = (Event.key.state == SDL_PRESSED);
|
|
WasDown = FALSE;
|
|
if(Event.key.state == SDL_RELEASED) {
|
|
WasDown = TRUE;
|
|
} else if(Event.key.repeat != 0) {
|
|
WasDown = TRUE;
|
|
}
|
|
|
|
if(Event.key.repeat == 0) {
|
|
if(KeyCode == SDLK_w) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.MoveUp,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_s) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.MoveDown,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_a) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.MoveLeft,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_d) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.MoveRight,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_q) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.LeftShoulder,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_e) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.RightShoulder,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_UP) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.ActionUp,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_DOWN) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.ActionDown,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_LEFT) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.ActionLeft,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_RIGHT) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.ActionRight,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_ESCAPE) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.Start,
|
|
IsDown);
|
|
} else if(KeyCode == SDLK_SPACE) {
|
|
SDLProcessKeyboardMessage(
|
|
&KeyboardController->u.buttons.Back,
|
|
IsDown);
|
|
}
|
|
}
|
|
|
|
if(WasDown) {
|
|
B32 AltKeyWasDown;
|
|
|
|
#if BUILD_INTERNAL
|
|
|
|
if(KeyCode == SDLK_l) {
|
|
if(SDLState->InputPlayingIndex == 0) {
|
|
if(SDLState->InputRecordingIndex == 0) {
|
|
SDLBeginRecordingInput(SDLState, 1);
|
|
} else {
|
|
SDLEndRecordingInput(SDLState);
|
|
SDLBeginInputPlayback(SDLState, 1);
|
|
}
|
|
} else {
|
|
SDLEndInputPlayback(SDLState);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* 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. */
|
|
AltKeyWasDown = (Event.key.keysym.mod & KMOD_ALT);
|
|
if(KeyCode == SDLK_F4 && AltKeyWasDown)
|
|
GlobalRunning = FALSE;
|
|
#if BUILD_INTERNAL
|
|
if(KeyCode == SDLK_ESCAPE)
|
|
GlobalRunning = FALSE;
|
|
#endif
|
|
}
|
|
} break;
|
|
|
|
default: {
|
|
HandleEvent(&Event);
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static F32 SDLProcessInputStickValue(F32 Value, S32 DeadZoneThreshold)
|
|
{
|
|
F32 Result;
|
|
|
|
Result = 0.0f;
|
|
if(Value < -(F32)DeadZoneThreshold)
|
|
Result = ((Value + (F32)DeadZoneThreshold) /
|
|
(32768.0f - (F32)DeadZoneThreshold));
|
|
else if(Value > (F32)DeadZoneThreshold)
|
|
Result = ((Value + (F32)DeadZoneThreshold) /
|
|
(32767.0f - (F32)DeadZoneThreshold));
|
|
|
|
return Result;
|
|
}
|
|
|
|
#define DEFAULT_REFRESH_RATE 60
|
|
static S32 SDLGetWindowRefreshRate(SDL_Window *Window)
|
|
{
|
|
S32 DisplayIndex;
|
|
SDL_DisplayMode Mode;
|
|
|
|
DisplayIndex = SDL_GetWindowDisplayIndex(Window);
|
|
|
|
if(SDL_GetDesktopDisplayMode(DisplayIndex, &Mode) != 0)
|
|
return DEFAULT_REFRESH_RATE;
|
|
if(Mode.refresh_rate == 0)
|
|
return DEFAULT_REFRESH_RATE;
|
|
|
|
return Mode.refresh_rate;
|
|
}
|
|
|
|
static U64 SDLGetWallClock()
|
|
{
|
|
return SDL_GetPerformanceCounter();
|
|
}
|
|
|
|
static F32 SDLGetSecondsElapsed(U64 Start, U64 End)
|
|
{
|
|
return (F32)(End - Start) / (F32)GlobalPerfCountFrequency;
|
|
}
|
|
|
|
typedef struct sdl_game_code {
|
|
void *GameCodeDLL;
|
|
U32 DLLLastWriteTime;
|
|
|
|
void (*UpdateAndRender)(thread_context *Thread, game_memory *,
|
|
game_input *, game_offscreen_buffer *,
|
|
game_sound_output_buffer *);
|
|
|
|
B32 IsValid;
|
|
} sdl_game_code;
|
|
|
|
static U32 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"); */
|
|
perror("stat failed");
|
|
}
|
|
|
|
return (U32)file_info.st_mtime;
|
|
}
|
|
|
|
#define GAME_DLL_NAME "libhandmade.so"
|
|
|
|
static sdl_game_code SDLLoadGameCode(char *DLLPath)
|
|
{
|
|
sdl_game_code Result;
|
|
memset(&Result, 0, sizeof(Result));
|
|
|
|
Result.GameCodeDLL = dlopen(DLLPath, RTLD_NOW);
|
|
if(Result.GameCodeDLL) {
|
|
Result.DLLLastWriteTime = GetLastWriteTime(DLLPath);
|
|
|
|
Result.UpdateAndRender =
|
|
(void(*)(thread_context *, game_memory *, game_input *,
|
|
game_offscreen_buffer *, game_sound_output_buffer *))
|
|
dlsym(Result.GameCodeDLL, "UpdateAndRender");
|
|
if(!Result.UpdateAndRender) {
|
|
/* TODO: Diagnostic. dlerror() */
|
|
}
|
|
|
|
Result.IsValid = Result.UpdateAndRender ? TRUE : FALSE;
|
|
} else {
|
|
/* TODO: Diagnostic. dlerror() */
|
|
}
|
|
|
|
if(!Result.IsValid) {
|
|
Result.UpdateAndRender = NULL;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static void SDLUnloadGameCode(sdl_game_code *GameCode)
|
|
{
|
|
if(GameCode->GameCodeDLL)
|
|
dlclose(GameCode->GameCodeDLL); /* NOTE: Might fail. */
|
|
GameCode->IsValid = FALSE;
|
|
GameCode->UpdateAndRender = NULL;
|
|
}
|
|
|
|
void GetExecutablePath(char *path, size_t path_size)
|
|
{
|
|
#if __APPLE__ && __MACH__
|
|
U32 size;
|
|
#else
|
|
ssize_t size;
|
|
#endif
|
|
|
|
if(path_size > 0) {
|
|
path[0] = '\0';
|
|
|
|
#if __APPLE__ && __MACH__
|
|
size = path_size;
|
|
if(_NSGetExecutablePath(path, &size) != 0) { /* NOTE: Not an
|
|
absolute
|
|
path. */
|
|
/* TODO: Diagnostics. */
|
|
}
|
|
#else
|
|
size = readlink("/proc/self/exe", path, path_size - 1);
|
|
if(size != -1) {
|
|
path[size] = '\0';
|
|
} else {
|
|
/* TODO: Diagnostics. */
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void SDLGetEXEDirPath(char *path, size_t path_size)
|
|
{
|
|
U32 len, i;
|
|
|
|
GetExecutablePath(path, path_size);
|
|
len = str_len(path);
|
|
i = len;
|
|
while(path[i] != '/' || 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 !(__x86_64__ || __i386__)
|
|
U64 __rdtsc()
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* TODO: In the future, this should just accept either a bitmap or image
|
|
* path. This is to be determined. One cool feature this might
|
|
* support is displaying certain information in the program icon by
|
|
* drawing into a bitmap. */
|
|
void SDLSetProgramIcon(SDL_Window *Window)
|
|
{
|
|
SDL_Surface *icon;
|
|
|
|
U32 Rmask, Gmask, Bmask, Amask;
|
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
S32 shift_by = (program_icon.bytes_per_pixel == 3) ? 8 : 0;
|
|
Rmask = 0xff000000 >> shift_by;
|
|
Gmask = 0x00ff0000 >> shift_by;
|
|
Bmask = 0x0000ff00 >> shift_by;
|
|
Amask = 0x000000ff >> shift_by;
|
|
#else
|
|
Rmask = 0x000000ff;
|
|
Gmask = 0x0000ff00;
|
|
Bmask = 0x00ff0000;
|
|
Amask = (program_icon.bytes_per_pixel == 3) ? 0 : 0xff000000;
|
|
#endif
|
|
|
|
icon = SDL_CreateRGBSurfaceFrom(
|
|
(void *)program_icon.pixel_data,
|
|
program_icon.width, program_icon.height,
|
|
program_icon.bytes_per_pixel * 8,
|
|
program_icon.bytes_per_pixel * program_icon.width,
|
|
Rmask, Gmask, Bmask, Amask);
|
|
if(icon) {
|
|
SDL_SetWindowIcon(Window, icon);
|
|
SDL_FreeSurface(icon);
|
|
} else {
|
|
/* TODO: This didn't work . . . SDL_GetError() */
|
|
}
|
|
}
|
|
|
|
S32 main(S32 argc, char *argv[])
|
|
{
|
|
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_GAMECONTROLLER |
|
|
SDL_INIT_AUDIO))
|
|
{
|
|
/* TODO: This didn't work . . . */
|
|
}
|
|
|
|
SDLInitControllers();
|
|
|
|
/* NOTE: Floating windows are not supported oficially by Wayland
|
|
* protocol. However, SDL_WINDOW_ALWAYS_ON_TOP does work on KDE
|
|
* under Wayland. I have not tested GNOME. */
|
|
Window = SDL_CreateWindow("Handmade Hero", SDL_WINDOWPOS_UNDEFINED,
|
|
SDL_WINDOWPOS_UNDEFINED,
|
|
RESOLUTION_WIDTH, RESOLUTION_HEIGHT,
|
|
SDL_WINDOW_RESIZABLE |
|
|
SDL_WINDOW_ALWAYS_ON_TOP);
|
|
|
|
if(Window) {
|
|
SDL_Renderer *Renderer;
|
|
|
|
SDL_DisplayMode display_mode;
|
|
SDL_GetCurrentDisplayMode(0, &display_mode);
|
|
|
|
Renderer = SDL_CreateRenderer(Window, -1, SDL_RENDERER_PRESENTVSYNC);
|
|
SDL_SetWindowPosition(Window, (display_mode.w - RESOLUTION_WIDTH),
|
|
(display_mode.h - RESOLUTION_HEIGHT));
|
|
|
|
/* "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) {
|
|
sdl_window_dimension Dimension;
|
|
sdl_sound_output SoundOutput;
|
|
game_memory GameMemory;
|
|
S32 ReplayIndex;
|
|
|
|
#if BUILD_DEBUG
|
|
void *BaseAddress = (void *)TB(2);
|
|
#else
|
|
void *BaseAddress = (void *)(0);
|
|
#endif
|
|
|
|
SDLSetProgramIcon(Window);
|
|
#if 0
|
|
if(SDL_ShowCursor(SDL_DISABLE) < 0) {
|
|
/* TODO: This didn't work . . . SDL_GetError() */
|
|
}
|
|
#endif
|
|
|
|
GlobalRunning = TRUE;
|
|
|
|
Dimension = SDLGetWindowDimension(Window);
|
|
SDLResizeTexture(&GlobalBackbuffer, Renderer, Dimension.Width,
|
|
Dimension.Height);
|
|
|
|
memset(&SoundOutput, 0, sizeof(SoundOutput));
|
|
SoundOutput.SamplesPerSecond = 48000;
|
|
SoundOutput.BytesPerSample = sizeof(S16) * 2;
|
|
SoundOutput.SecondaryBufferSize =
|
|
SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample;
|
|
SoundOutput.tSine = 0;
|
|
SoundOutput.LatencySampleCount =
|
|
SoundOutput.SamplesPerSecond / 15;
|
|
|
|
SDLInitSound(SoundOutput.SamplesPerSecond,
|
|
SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample /
|
|
60);
|
|
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.DEBUGPlatformWriteEntireFile =
|
|
&DEBUGPlatformWriteEntireFile;
|
|
GameMemory.DEBUGPlatformFreeFileMemory =
|
|
&DEBUGPlatformFreeFileMemory;
|
|
|
|
SDLState.TotalSize =
|
|
GameMemory.PermanentStorageSize +
|
|
GameMemory.TransientStorageSize;
|
|
/* 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);
|
|
GameMemory.PermanentStorage = SDLState.GameMmemoryBlock;
|
|
GameMemory.TransientStorage =
|
|
(U8 *)(GameMemory.PermanentStorage) +
|
|
GameMemory.PermanentStorageSize;
|
|
|
|
for(ReplayIndex = 0;
|
|
ReplayIndex < (S32)ARRAY_SIZE(SDLState.ReplayBuffers);
|
|
ReplayIndex++)
|
|
{
|
|
sdl_replay_buffer *ReplayBuffer =
|
|
&SDLState.ReplayBuffers[ReplayIndex];
|
|
ReplayBuffer->MemoryBlock = mmap(NULL,
|
|
(size_t)SDLState.TotalSize,
|
|
PROT_READ | PROT_WRITE,
|
|
MAP_ANON | MAP_PRIVATE,
|
|
-1, 0);
|
|
if(ReplayBuffer->MemoryBlock) {
|
|
} else {
|
|
/* TODO: Change this to log message. */
|
|
}
|
|
}
|
|
|
|
if(SoundOutput.Samples &&
|
|
GameMemory.PermanentStorage &&
|
|
GameMemory.TransientStorage)
|
|
{
|
|
game_input Input[2];
|
|
game_input *NewInput, *OldInput;
|
|
sdl_game_code Game;
|
|
F32 TargetSecondsPerFrame;
|
|
S32 MonitorRefreshHz, GameUpdateHz;
|
|
|
|
U64 LastCounter;
|
|
U64 LastCycleCount;
|
|
|
|
MonitorRefreshHz = SDLGetWindowRefreshRate(Window);
|
|
/*GameUpdateHz = MonitorRefreshHz;*/
|
|
GameUpdateHz = 30; /* NOTE: Temporarily target 30 FPS. */
|
|
TargetSecondsPerFrame = 1.0f / (F32)GameUpdateHz;
|
|
|
|
NewInput = &Input[0];
|
|
OldInput = &Input[1];
|
|
memset(&Input, 0, sizeof(Input));
|
|
|
|
GlobalPerfCountFrequency = SDL_GetPerformanceFrequency();
|
|
|
|
Game = SDLLoadGameCode(SDLState.DLLPath);
|
|
|
|
while(GlobalRunning) {
|
|
thread_context Context;
|
|
U32 NewDLLWriteTime, ButtonIndex, ControllerIndex,
|
|
SDLMouseButtons;
|
|
game_controller_input *OldKeyboardController,
|
|
*NewKeyboardController;
|
|
game_controller_input ZeroController;
|
|
S32 TargetQueueBytes, BytesToWrite;
|
|
game_sound_output_buffer SoundBuffer;
|
|
game_offscreen_buffer Buffer;
|
|
U64 WorkCounter, EndCounter;
|
|
F32 WorkSecondsElapsed, SecondsElapsedForFrame;
|
|
F64 MSPerFrame, FPS;
|
|
U64 EndCycleCount, CyclesElapsed;
|
|
F64 MCPF;
|
|
|
|
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);
|
|
}
|
|
|
|
SDLMouseButtons = SDL_GetMouseState(&(NewInput->MouseX),
|
|
&(NewInput->MouseY));
|
|
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 = TRUE;
|
|
|
|
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++)
|
|
{
|
|
game_controller_input *OldController, *NewController;
|
|
|
|
OldController =
|
|
GetController(OldInput, ControllerIndex+1);
|
|
NewController =
|
|
GetController(NewInput, ControllerIndex+1);
|
|
|
|
if(ControllerHandles[ControllerIndex] != 0 &&
|
|
SDL_GameControllerGetAttached(
|
|
ControllerHandles[ControllerIndex]))
|
|
{
|
|
S16 StickX, StickY;
|
|
float Threshold;
|
|
|
|
NewController->IsAnalog =
|
|
OldController->IsAnalog;
|
|
NewController->IsConnected = TRUE;
|
|
|
|
StickX =
|
|
SDL_GameControllerGetAxis(
|
|
ControllerHandles[ControllerIndex],
|
|
SDL_CONTROLLER_AXIS_LEFTX);
|
|
StickY =
|
|
SDL_GameControllerGetAxis(
|
|
ControllerHandles[ControllerIndex],
|
|
SDL_CONTROLLER_AXIS_LEFTY);
|
|
|
|
NewController->IsAnalog = TRUE;
|
|
NewController->StickAverageX =
|
|
SDLProcessInputStickValue((F32)StickX,
|
|
CONTROLLER_LEFT_THUMB_DEADZONE);
|
|
NewController->StickAverageY =
|
|
SDLProcessInputStickValue((F32)StickY,
|
|
CONTROLLER_LEFT_THUMB_DEADZONE);
|
|
|
|
if((NewController->StickAverageX != 0.0f) ||
|
|
(NewController->StickAverageY != 0.0f))
|
|
{
|
|
NewController->IsAnalog = TRUE;
|
|
}
|
|
|
|
if(SDL_GameControllerGetButton(
|
|
ControllerHandles[ControllerIndex],
|
|
SDL_CONTROLLER_BUTTON_DPAD_UP))
|
|
{
|
|
NewController->StickAverageY = -1.0f;
|
|
NewController->IsAnalog = FALSE;
|
|
}
|
|
|
|
if(SDL_GameControllerGetButton(
|
|
ControllerHandles[ControllerIndex],
|
|
SDL_CONTROLLER_BUTTON_DPAD_DOWN))
|
|
{
|
|
NewController->StickAverageY = 1.0f;
|
|
NewController->IsAnalog = FALSE;
|
|
}
|
|
|
|
if(SDL_GameControllerGetButton(
|
|
ControllerHandles[ControllerIndex],
|
|
SDL_CONTROLLER_BUTTON_DPAD_LEFT))
|
|
{
|
|
NewController->StickAverageX = -1.0f;
|
|
NewController->IsAnalog = FALSE;
|
|
}
|
|
|
|
if(SDL_GameControllerGetButton(
|
|
ControllerHandles[ControllerIndex],
|
|
SDL_CONTROLLER_BUTTON_DPAD_RIGHT))
|
|
{
|
|
NewController->StickAverageX = 1.0f;
|
|
NewController->IsAnalog = FALSE;
|
|
}
|
|
|
|
Threshold = 0.5f;
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[
|
|
(NewController->StickAverageY <
|
|
-Threshold) ? 1 : 0],
|
|
&OldController->u.buttons.MoveUp,
|
|
SDL_CONTROLLER_BUTTON_A,
|
|
&NewController->u.buttons.MoveUp);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[
|
|
(NewController->StickAverageY >
|
|
Threshold) ? 1 : 0],
|
|
&OldController->u.buttons.MoveDown,
|
|
SDL_CONTROLLER_BUTTON_A,
|
|
&NewController->u.buttons.MoveDown);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[
|
|
(NewController->StickAverageX <
|
|
-Threshold) ? 1 : 0],
|
|
&OldController->u.buttons.MoveLeft,
|
|
SDL_CONTROLLER_BUTTON_A,
|
|
&NewController->u.buttons.MoveLeft);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[
|
|
(NewController->StickAverageX >
|
|
Threshold) ? 1 : 0],
|
|
&OldController->u.buttons.MoveRight,
|
|
SDL_CONTROLLER_BUTTON_A,
|
|
&NewController->u.buttons.MoveRight);
|
|
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[ControllerIndex],
|
|
&OldController->u.buttons.ActionDown,
|
|
SDL_CONTROLLER_BUTTON_A,
|
|
&NewController->u.buttons.ActionDown);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[ControllerIndex],
|
|
&OldController->u.buttons.ActionRight,
|
|
SDL_CONTROLLER_BUTTON_B,
|
|
&NewController->u.buttons.ActionRight);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[ControllerIndex],
|
|
&OldController->u.buttons.ActionLeft,
|
|
SDL_CONTROLLER_BUTTON_X,
|
|
&NewController->u.buttons.ActionLeft);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[ControllerIndex],
|
|
&OldController->u.buttons.ActionUp,
|
|
SDL_CONTROLLER_BUTTON_Y,
|
|
&NewController->u.buttons.ActionUp);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[ControllerIndex],
|
|
&OldController->u.buttons.LeftShoulder,
|
|
SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
|
&NewController->u.buttons.LeftShoulder);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[ControllerIndex],
|
|
&OldController->u.buttons.RightShoulder,
|
|
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
|
&NewController->u.buttons.RightShoulder);
|
|
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[ControllerIndex],
|
|
&OldController->u.buttons.Start,
|
|
SDL_CONTROLLER_BUTTON_START,
|
|
&NewController->u.buttons.Start);
|
|
SDLProcessInputDigitalButton(
|
|
ControllerHandles[ControllerIndex],
|
|
&OldController->u.buttons.Back,
|
|
SDL_CONTROLLER_BUTTON_BACK,
|
|
&NewController->u.buttons.Back);
|
|
} else {
|
|
/* NOTE: This controller is not plugged in. */
|
|
NewController->IsConnected = FALSE;
|
|
}
|
|
}
|
|
|
|
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(SDLState.InputRecordingIndex) {
|
|
SDLRecordInput(&SDLState, NewInput);
|
|
}
|
|
if(SDLState.InputPlayingIndex) {
|
|
SDLPlaybackInput(&SDLState, NewInput);
|
|
}
|
|
|
|
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());
|
|
SDL_Delay((U32)((TargetSecondsPerFrame -
|
|
SecondsElapsedForFrame) * 1000.0));
|
|
}
|
|
} else {
|
|
/* TODO: Missed frame rate! */
|
|
/* TODO: Logging. */
|
|
}
|
|
|
|
EndCounter = SDLGetWallClock();
|
|
|
|
SDLDisplayBufferInWindow(GlobalBackbuffer, Window,
|
|
Renderer);
|
|
|
|
MSPerFrame = 1000.0 * SDLGetSecondsElapsed(LastCounter,
|
|
EndCounter);
|
|
FPS = 0.0;
|
|
|
|
LastCounter = EndCounter;
|
|
|
|
#if 0
|
|
EndCycleCount = __rdtsc();
|
|
CyclesElapsed = EndCycleCount - LastCycleCount;
|
|
MCPF = ((double)CyclesElapsed / (1000.0 * 1000.0));
|
|
fprintf(stderr, "%.02f ms/f, %.02f/s, %.02f mc/f\n",
|
|
MSPerFrame, FPS, MCPF);
|
|
LastCycleCount = EndCycleCount;
|
|
#endif
|
|
|
|
SWAP(game_input *, NewInput, OldInput);
|
|
}
|
|
} 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;
|
|
}
|