596 lines
24 KiB
C++
596 lines
24 KiB
C++
#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
|
|
#include <stdio.h>
|
|
|
|
#include <sys/stat.h> // fstat
|
|
#include <fcntl.h> // open
|
|
#include <unistd.h> // close, read
|
|
#include <dlfcn.h> // dlopen
|
|
|
|
// NOTE: This is so it compiles on ARM.
|
|
#if __x86_64__ || __i386__
|
|
#include <x86intrin.h>
|
|
#endif
|
|
|
|
static bool GlobalRunning;
|
|
|
|
static U64 GlobalPerfCountFrequency;
|
|
|
|
#define CONTROLLER_LEFT_THUMB_DEADZONE 8000
|
|
#define MAX_CONTROLLERS 4
|
|
SDL_GameController *ControllerHandles[MAX_CONTROLLERS];
|
|
|
|
static offscreen_buffer GlobalBackbuffer;
|
|
|
|
static U32 SafeTruncateUInt64(U64 Value)
|
|
{
|
|
Assert(Value <= 0xFFFFFFFF);
|
|
U32 Result = (U32)Value;
|
|
return Result;
|
|
}
|
|
|
|
debug_read_file_result DEBUGPlatformReadEntireFile(const char *Filename)
|
|
{
|
|
debug_read_file_result Result = {0};
|
|
S32 FileHandle = open(Filename, O_RDONLY);
|
|
if(FileHandle == -1) {
|
|
return Result;
|
|
}
|
|
|
|
struct stat FileStatus;
|
|
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;
|
|
}
|
|
|
|
U32 BytesToRead = Result.ContentsSize;
|
|
U8 *NextByteLocation = (U8 *)Result.Contents;
|
|
while(BytesToRead) {
|
|
U32 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(const char *Filename, U32 MemorySize, void *Memory)
|
|
{
|
|
S32 FileHandle = open(Filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if(FileHandle == -1)
|
|
return false;
|
|
|
|
U32 BytesToWrite = MemorySize;
|
|
U8 *NextByteLocation = (U8*)Memory;
|
|
while(BytesToWrite) {
|
|
U32 BytesWritten = write(FileHandle, NextByteLocation, BytesToWrite);
|
|
if(BytesWritten == (U32)-1) {
|
|
close(FileHandle);
|
|
return false;
|
|
}
|
|
BytesToWrite -= BytesWritten;
|
|
NextByteLocation += BytesWritten;
|
|
}
|
|
|
|
close(FileHandle);
|
|
|
|
return true;
|
|
}
|
|
|
|
void DEBUGPlatformFreeFileMemory(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)
|
|
{
|
|
if(Buffer->Texture)
|
|
SDL_DestroyTexture(Buffer->Texture);
|
|
|
|
if(Buffer->Memory)
|
|
free(Buffer->Memory);
|
|
|
|
S32 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)
|
|
{
|
|
if(SDL_UpdateTexture(Buffer.Texture, 0, Buffer.Memory, Buffer.Pitch)) {
|
|
// TODO: Do something about this error!
|
|
}
|
|
|
|
SDL_RenderCopy(Renderer, Buffer.Texture, 0, 0);
|
|
SDL_RenderPresent(Renderer);
|
|
}
|
|
|
|
static void SDLInitControllers()
|
|
{
|
|
int MaxJoysticks = SDL_NumJoysticks();
|
|
int ControllerIndex = 0;
|
|
for(int 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(S32 SamplesPerSecond, S32 BufferSize)
|
|
{
|
|
SDL_AudioSpec AudioSettings = {0};
|
|
|
|
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, bool IsDown)
|
|
{
|
|
Assert(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)
|
|
{
|
|
switch(Event->type) {
|
|
case SDL_QUIT: {
|
|
GlobalRunning = false;
|
|
} break;
|
|
|
|
case SDL_WINDOWEVENT: {
|
|
switch(Event->window.event) {
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED: {
|
|
SDL_Window *Window = SDL_GetWindowFromID(Event->window.windowID);
|
|
SDL_Renderer *Renderer = SDL_GetRenderer(Window);
|
|
|
|
sdl_window_dimension Dimension = SDLGetWindowDimension(Window);
|
|
SDLResizeTexture(&GlobalBackbuffer, Renderer, Dimension.Width, Dimension.Height);
|
|
} break;
|
|
|
|
case SDL_WINDOWEVENT_EXPOSED: {
|
|
} break;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
static void SDLProcessMessages(game_controller_input *KeyboardController)
|
|
{
|
|
SDL_Event Event;
|
|
while(SDL_PollEvent(&Event)) {
|
|
switch(Event.type) {
|
|
case SDL_KEYDOWN:
|
|
case SDL_KEYUP: {
|
|
SDL_Keycode KeyCode = Event.key.keysym.sym;
|
|
bool IsDown = (Event.key.state == SDL_PRESSED);
|
|
bool 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->MoveUp, IsDown);
|
|
} else if(KeyCode == SDLK_s) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->MoveDown, IsDown);
|
|
} else if(KeyCode == SDLK_a) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->MoveLeft, IsDown);
|
|
} else if(KeyCode == SDLK_d) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->MoveRight, IsDown);
|
|
} else if(KeyCode == SDLK_q) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->LeftShoulder, IsDown);
|
|
} else if(KeyCode == SDLK_e) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->RightShoulder, IsDown);
|
|
} else if(KeyCode == SDLK_UP) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->ActionUp, IsDown);
|
|
} else if(KeyCode == SDLK_DOWN) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->ActionDown, IsDown);
|
|
} else if(KeyCode == SDLK_LEFT) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->ActionLeft, IsDown);
|
|
} else if(KeyCode == SDLK_RIGHT) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->ActionRight, IsDown);
|
|
} else if(KeyCode == SDLK_ESCAPE) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->Start, IsDown);
|
|
} else if(KeyCode == SDLK_SPACE) {
|
|
SDLProcessKeyboardMessage(&KeyboardController->Back, IsDown);
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
bool AltKeyWasDown = (Event.key.keysym.mod & KMOD_ALT);
|
|
if(KeyCode == SDLK_F4 && AltKeyWasDown)
|
|
GlobalRunning = false;
|
|
if(KeyCode == SDLK_ESCAPE)
|
|
GlobalRunning = false;
|
|
} break;
|
|
|
|
default: {
|
|
HandleEvent(&Event);
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static F32 SDLProcessInputStickValue(F32 Value, S32 DeadZoneThreshold)
|
|
{
|
|
F32 Result = 0.0f;
|
|
if(Value < -DeadZoneThreshold)
|
|
Result = (F32)((Value + DeadZoneThreshold) / (32768.0f - DeadZoneThreshold));
|
|
else if(Value > DeadZoneThreshold)
|
|
Result = (F32)((Value + DeadZoneThreshold) / (32767.0f - DeadZoneThreshold));
|
|
return Result;
|
|
}
|
|
|
|
#define DEFAULT_REFRESH_RATE 60
|
|
static S32 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 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;
|
|
void (*UpdateAndRender)(game_memory *, game_input *, game_offscreen_buffer *, game_sound_output_buffer *);
|
|
|
|
B32 IsValid;
|
|
} sdl_game_code;
|
|
|
|
static sdl_game_code SDLLoadGameCode()
|
|
{
|
|
sdl_game_code Result = {0};
|
|
|
|
Result.GameCodeDLL = dlopen("./libhandmade.so", RTLD_NOW);
|
|
if(Result.GameCodeDLL) {
|
|
Result.UpdateAndRender = (void(*)(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;
|
|
}
|
|
|
|
S32 main(S32 argc, char *argv[])
|
|
{
|
|
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO)) {
|
|
/* TODO: This didn't work . . . */
|
|
}
|
|
|
|
SDLInitControllers();
|
|
|
|
SDL_Window *Window = SDL_CreateWindow("Handmade Hero", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 700, 500, SDL_WINDOW_RESIZABLE);
|
|
|
|
if(Window) {
|
|
//SDL_Renderer *Renderer = SDL_CreateRenderer(Window, -1, 0);
|
|
SDL_Renderer *Renderer = SDL_CreateRenderer(Window, -1, SDL_RENDERER_PRESENTVSYNC);
|
|
// "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) {
|
|
GlobalRunning = true;
|
|
|
|
sdl_window_dimension Dimension = SDLGetWindowDimension(Window);
|
|
SDLResizeTexture(&GlobalBackbuffer, Renderer, Dimension.Width, Dimension.Height);
|
|
|
|
sdl_sound_output SoundOutput = {0};
|
|
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);
|
|
// SDLClearSoundBuffer(&SoundOutput); // NOTE: calloc auto clears to zero
|
|
|
|
#if BUILD_DEBUG
|
|
void *BaseAddress = (void *)TB(2);
|
|
#else
|
|
void *BaseAddress = (void *)(0);
|
|
#endif
|
|
|
|
game_memory GameMemory = {0};
|
|
|
|
GameMemory.PermanentStorageSize = MB(64);
|
|
GameMemory.TransientStorageSize = GB(4);
|
|
U64 TotalStorageSize = GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize;
|
|
|
|
// NOTE: On MacOS and Linux, mmap seems to zero-fill anonymous memory as a side-effect of security.
|
|
GameMemory.PermanentStorage = mmap(BaseAddress, TotalStorageSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
|
GameMemory.TransientStorage = (U8 *)(GameMemory.PermanentStorage) + GameMemory.PermanentStorageSize;
|
|
|
|
GameMemory.DEBUGPlatformReadEntireFile = &DEBUGPlatformReadEntireFile;
|
|
GameMemory.DEBUGPlatformWriteEntireFile = &DEBUGPlatformWriteEntireFile;
|
|
GameMemory.DEBUGPlatformFreeFileMemory = &DEBUGPlatformFreeFileMemory;
|
|
|
|
if(SoundOutput.Samples && GameMemory.PermanentStorage && GameMemory.TransientStorage) {
|
|
game_input Input[2];
|
|
game_input *NewInput = &Input[0];
|
|
game_input *OldInput = &Input[1];
|
|
memset(&Input, 0, sizeof(Input));
|
|
|
|
GlobalPerfCountFrequency = SDL_GetPerformanceFrequency();
|
|
|
|
U64 LastCounter;
|
|
#if __x86_64__ || __i386__
|
|
U64 LastCycleCount;
|
|
#endif
|
|
|
|
sdl_game_code Game = SDLLoadGameCode();
|
|
U32 LoadCounter = 0;
|
|
|
|
while(GlobalRunning) {
|
|
LastCounter = SDLGetWallClock();
|
|
|
|
#if __x86_64__ || __i386__
|
|
LastCycleCount = __rdtsc(); // NOTE: rdtsc reports clock cylces, however it is not meant for really precise profiler work as the value returned is varied.
|
|
#endif // Also, __rdtsc is not available on MacOS ARM; I have not checked MacOS x64.
|
|
|
|
S32 MonitorRefreshHz = SDLGetWindowRefreshRate(Window);
|
|
S32 GameUpdateHz = MonitorRefreshHz / 2;
|
|
F32 TargetSecondsPerFrame = 1.0f / (F32)GameUpdateHz;
|
|
|
|
if(LoadCounter > (U32)(GameUpdateHz * 2)) {
|
|
SDLUnloadGameCode(&Game);
|
|
Game = SDLLoadGameCode();
|
|
LoadCounter = 0;
|
|
}
|
|
LoadCounter++;
|
|
|
|
game_controller_input *OldKeyboardController = GetController(OldInput, 0);
|
|
game_controller_input *NewKeyboardController = GetController(NewInput, 0);
|
|
game_controller_input ZeroController = {0};
|
|
*NewKeyboardController = ZeroController;
|
|
NewKeyboardController->IsConnected = true;
|
|
|
|
for(unsigned int ButtonIndex = 0; ButtonIndex < ARRAY_SIZE(NewKeyboardController->Buttons); ButtonIndex++) {
|
|
NewKeyboardController->Buttons[ButtonIndex].EndedDown = OldKeyboardController->Buttons[ButtonIndex].EndedDown;
|
|
}
|
|
|
|
SDLProcessMessages(NewKeyboardController);
|
|
|
|
for(unsigned int ControllerIndex = 0; ControllerIndex < MAX_CONTROLLERS; ControllerIndex++) {
|
|
game_controller_input *OldController = GetController(OldInput, ControllerIndex+1);
|
|
game_controller_input *NewController = GetController(NewInput, ControllerIndex+1);
|
|
|
|
if(ControllerHandles[ControllerIndex] != 0 && SDL_GameControllerGetAttached(ControllerHandles[ControllerIndex])) {
|
|
NewController->IsConnected = true;
|
|
|
|
S16 StickX = SDL_GameControllerGetAxis(ControllerHandles[ControllerIndex], SDL_CONTROLLER_AXIS_LEFTX);
|
|
S16 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;
|
|
}
|
|
|
|
float Threshold = 0.5f;
|
|
SDLProcessInputDigitalButton(ControllerHandles[(NewController->StickAverageY < -Threshold) ? 1 : 0], &OldController->MoveUp, SDL_CONTROLLER_BUTTON_A, &NewController->MoveUp);
|
|
SDLProcessInputDigitalButton(ControllerHandles[(NewController->StickAverageY > Threshold) ? 1 : 0], &OldController->MoveDown, SDL_CONTROLLER_BUTTON_A, &NewController->MoveDown);
|
|
SDLProcessInputDigitalButton(ControllerHandles[(NewController->StickAverageX < -Threshold) ? 1 : 0], &OldController->MoveLeft, SDL_CONTROLLER_BUTTON_A, &NewController->MoveLeft);
|
|
SDLProcessInputDigitalButton(ControllerHandles[(NewController->StickAverageX > Threshold) ? 1 : 0], &OldController->MoveRight, SDL_CONTROLLER_BUTTON_A, &NewController->MoveRight);
|
|
|
|
SDLProcessInputDigitalButton(ControllerHandles[ControllerIndex], &OldController->ActionDown, SDL_CONTROLLER_BUTTON_A, &NewController->ActionDown);
|
|
SDLProcessInputDigitalButton(ControllerHandles[ControllerIndex], &OldController->ActionRight, SDL_CONTROLLER_BUTTON_B, &NewController->ActionRight);
|
|
SDLProcessInputDigitalButton(ControllerHandles[ControllerIndex], &OldController->ActionLeft, SDL_CONTROLLER_BUTTON_X, &NewController->ActionLeft);
|
|
SDLProcessInputDigitalButton(ControllerHandles[ControllerIndex], &OldController->ActionUp, SDL_CONTROLLER_BUTTON_Y, &NewController->ActionUp);
|
|
SDLProcessInputDigitalButton(ControllerHandles[ControllerIndex], &OldController->LeftShoulder, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, &NewController->LeftShoulder);
|
|
SDLProcessInputDigitalButton(ControllerHandles[ControllerIndex], &OldController->RightShoulder, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, &NewController->RightShoulder);
|
|
|
|
SDLProcessInputDigitalButton(ControllerHandles[ControllerIndex], &OldController->Start, SDL_CONTROLLER_BUTTON_START, &NewController->Start);
|
|
SDLProcessInputDigitalButton(ControllerHandles[ControllerIndex], &OldController->Back, SDL_CONTROLLER_BUTTON_BACK, &NewController->Back);
|
|
} else {
|
|
// NOTE: This controller is not plugged in.
|
|
NewController->IsConnected = false;
|
|
}
|
|
}
|
|
|
|
S32 TargetQueueBytes = SoundOutput.LatencySampleCount * SoundOutput.BytesPerSample;
|
|
S32 BytesToWrite = TargetQueueBytes - SDL_GetQueuedAudioSize(1);
|
|
game_sound_output_buffer SoundBuffer;
|
|
SoundBuffer.SamplesPerSecond = SoundOutput.SamplesPerSecond;
|
|
SoundBuffer.SampleCount = BytesToWrite / SoundOutput.BytesPerSample;
|
|
SoundBuffer.Samples = (short *)SoundOutput.Samples;
|
|
|
|
game_offscreen_buffer Buffer;
|
|
Buffer.Memory = GlobalBackbuffer.Memory;
|
|
Buffer.Width = GlobalBackbuffer.Width;
|
|
Buffer.Height = GlobalBackbuffer.Height;
|
|
Buffer.Pitch = GlobalBackbuffer.Pitch;
|
|
if(Game.UpdateAndRender)
|
|
Game.UpdateAndRender(&GameMemory, NewInput, &Buffer, &SoundBuffer);
|
|
SDLFillSoundBuffer(&SoundOutput, BytesToWrite);
|
|
|
|
U64 WorkCounter = SDLGetWallClock();
|
|
F32 WorkSecondsElapsed = SDLGetSecondsElapsed(LastCounter, WorkCounter);
|
|
|
|
F32 SecondsElapsedForFrame = WorkSecondsElapsed;
|
|
if(SecondsElapsedForFrame < TargetSecondsPerFrame) {
|
|
while(SecondsElapsedForFrame < TargetSecondsPerFrame) {
|
|
SecondsElapsedForFrame = SDLGetSecondsElapsed(LastCounter, SDLGetWallClock());
|
|
SDL_Delay((unsigned int)((TargetSecondsPerFrame - SecondsElapsedForFrame) * 1000.0));
|
|
}
|
|
} else {
|
|
// TODO: Missed frame rate!
|
|
// TODO: Logging.
|
|
}
|
|
|
|
U64 EndCounter = SDLGetWallClock();
|
|
|
|
SDLDisplayBufferInWindow(GlobalBackbuffer, Window, Renderer);
|
|
|
|
F64 MSPerFrame = 1000.0 * SDLGetSecondsElapsed(LastCounter, EndCounter);
|
|
F64 FPS = 0.0;
|
|
|
|
LastCounter = EndCounter;
|
|
|
|
#if __x86_64__ || __i386__
|
|
U64 EndCycleCount = __rdtsc();
|
|
U64 CyclesElapsed = EndCycleCount - LastCycleCount;
|
|
F64 MCPF = ((double)CyclesElapsed / (1000.0 * 1000.0));
|
|
fprintf(stderr, "%.02f ms/f, %.02f/s, %.02f mc/f\n", MSPerFrame, FPS, MCPF);
|
|
LastCycleCount = EndCycleCount;
|
|
#else
|
|
fprintf(stderr, "%.02f ms/f, %.02f/s\n", MSPerFrame, FPS);
|
|
#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.
|
|
// SDLDeinitControllers(); // NOTE: This, however, might need to be closed? Is there maybe some clean up state that close communicates to the controller?
|
|
// SDL_CloseAudio();
|
|
// SDL_DestroyRenderer(Renderer);
|
|
// SDL_DestroyWindow(Window);
|
|
// SDL_Quit();
|
|
|
|
return 0;
|
|
}
|