/* NOTE: This layer only supports MacOS and Linux, for now. */ #include "core.h" #include "handmade.h" #include "sdl_handmade.h" #include #include /* mmap */ #include /* malloc, calloc */ #include /* memset, memcpy */ #include #include /* fstat */ #include /* open */ #include /* close, write, read, readlink, unlink */ #include /* dlopen */ #include /* stat */ #if __APPLE__ && __MACH__ #include /* _NSGetExecutablePath */ #endif /* NOTE: This is so it compiles on ARM. */ #if __x86_64__ || __i386__ #include #endif 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; 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 = 0; dest_rect.y = 0; dest_rect.w = 500; dest_rect.h = 500; 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 < -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) { 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); } 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, 500, 500, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALWAYS_ON_TOP); if(Window) { SDL_Renderer *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) { 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 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; #if __x86_64__ || __i386__ U64 LastCycleCount; #endif MonitorRefreshHz = SDLGetWindowRefreshRate(Window); GameUpdateHz = MonitorRefreshHz; 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; #if __x86_64__ || __i386__ U64 EndCycleCount, CyclesElapsed; F64 MCPF; #endif LastCounter = SDLGetWallClock(); #if __x86_64__ || __i386__ /* 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(); #endif 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.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 #if __x86_64__ || __i386__ 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; #else fprintf(stderr, "%.02f ms/f, %.02f/s\n", MSPerFrame, FPS); #endif #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; }