#include "core.h" // Remove SDL's entry point. #if OS_WINDOWS #define SDL_MAIN_HANDLED #endif #include "handmade.h" #include "sdl_handmade.h" #include #include /* malloc, calloc */ #include /* memset, memcpy */ #include #include /* fstat */ #include /* open */ #include /* stat */ #if OS_MACOS || OS_LINUX #include /* dlopen */ #include /* mmap */ #include /* close, write, read, readlink, unlink */ #endif #if OS_WINDOWS #include /* VirtualAlloc */ #endif #if OS_MACOS #include /* _NSGetExecutablePath */ #endif /* NOTE: This is so it compiles on ARM. */ #if ARCHITECTURE_X64 || ARCHITECTURE_X86 #if OS_MACOS || OS_LINUX #include // TODO: What is the equivalent on Windows? #endif #endif #if 0 #include "program_icon.c" #endif #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 B32 IsFullscreen; static sdl_window_size WindowSize; static sdl_window_position WindowPosition; static S32 str_len(char *str) { S32 count = 0; while(*str++) count++; return count; } static U32 SafeTruncateUInt64(U64 Value) { ASSERT(Value <= 0xFFFFFFFF); U32 Result = (U32)Value; return Result; } debug_read_file_result DEBUGPlatformReadEntireFile(thread_context *Thread, const char *Filename) { (void)Thread; debug_read_file_result Result; memset(&Result, 0, sizeof(Result)); SDL_RWops *FileHandle = SDL_RWFromFile(Filename, "r"); if(FileHandle) { S64 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; } size_t ObjectsRead = SDL_RWread(FileHandle, (void *)Result.Contents, (size_t)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; } B32 DEBUGPlatformWriteEntireFile(thread_context *Thread, const char *Filename, U32 MemorySize, void *Memory) { SDL_RWops *FileHandle = SDL_RWFromFile(Filename, "w"); if(FileHandle) { size_t ObjectsWritten = SDL_RWwrite(FileHandle, (void *)Memory, (size_t)MemorySize, 1); if(ObjectsWritten == 0) { SDL_RWclose(FileHandle); return FALSE; } SDL_RWclose(FileHandle); return TRUE; } return FALSE; } void DEBUGPlatformFreeFileMemory(thread_context *Thread, void *Memory) { if(Memory) free(Memory); // TODO: Should we accept debug_read_file_result instead of just memory. } static sdl_window_size SDLGetWindowSize(SDL_Window *Window) { sdl_window_size Dimension; SDL_GetWindowSize(Window, &Dimension.Width, &Dimension.Height); return Dimension; } static sdl_window_position SDLGetWindowPosition(SDL_Window *Window) { // NOTE: SDL_GetWindowPosition returns the client area position. S32 top, left; SDL_GetWindowBordersSize(Window, &top, &left, 0, 0); sdl_window_position Position; SDL_GetWindowPosition(Window, &Position.X, &Position.Y); Position.X -= left; Position.Y -= top; return Position; } 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) { 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); */ sdl_window_size WinSize = SDLGetWindowSize(Window); if(WinSize.Width >= Buffer->Width*2 && WinSize.Height >= Buffer->Height*2) { SDL_Rect dest_rect = { OffsetX, OffsetY, Buffer->Width*2, Buffer->Height*2 }; SDL_RenderCopy(Renderer, Buffer->Texture, 0, (const SDL_Rect *)(&dest_rect)); } else { SDL_Rect dest_rect = { OffsetX, OffsetY, RESOLUTION_WIDTH, RESOLUTION_HEIGHT }; SDL_RenderCopy(Renderer, Buffer->Texture, 0, (const SDL_Rect *)(&dest_rect)); } SDL_RenderPresent(Renderer); } static void SDLInitControllers() { S32 MaxJoysticks = SDL_NumJoysticks(); S32 ControllerIndex = 0; for(S32 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(S32 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 = (U16)BufferSize; // TODO: Unsafe truncate from S32 to U16 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) { SDL_Window *Window = SDL_GetWindowFromID(Event->window.windowID); SDL_Renderer *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 /* 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) { if(IsFullscreen) { if(SDL_SetWindowFullscreen(Window, 0) == 0) { SDL_SetWindowSize(Window, WindowSize.Width, WindowSize.Height); SDL_SetWindowPosition(Window, WindowPosition.X, WindowPosition.Y); IsFullscreen = FALSE; } else { /* TODO: This didn't work . . . SDL_GetError() */ } } else { WindowSize = SDLGetWindowSize(Window); WindowPosition = SDLGetWindowPosition(Window); if(SDL_SetWindowFullscreen(Window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) { IsFullscreen = TRUE; } else { /* TODO: This didn't work . . . SDL_GetError() */ } } } static void SDLProcessMessages(sdl_state *SDLState, 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; 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->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); } } 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. */ B32 AltKeyWasDown = (Event.key.keysym.mod & KMOD_ALT); if(KeyCode == SDLK_F4 && AltKeyWasDown) GlobalRunning = FALSE; #if BUILD_INTERNAL if(KeyCode == SDLK_ESCAPE) GlobalRunning = FALSE; #endif if((KeyCode == SDLK_RETURN) && AltKeyWasDown) { SDLToggleFullscreen(Window); } } } break; default: { HandleEvent(&Event); } break; } } } static F32 SDLProcessInputStickValue(F32 Value, S32 DeadZoneThreshold) { F32 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_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; 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"); */ } return (U32)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 sdl_game_code SDLLoadGameCode(char *DLLPath) { 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(*)(thread_context *, game_memory *, game_input *, game_offscreen_buffer *, 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 ? TRUE : FALSE; } else { /* TODO: Diagnostic. dlerror() */ } if(!Result.IsValid) { Result.UpdateAndRender = NULL; } return Result; } static void SDLUnloadGameCode(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 = FALSE; GameCode->UpdateAndRender = NULL; } void GetExecutablePath(char *path, size_t path_size) { #if OS_MACOS U32 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) { /* NOTE: 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) { GetExecutablePath(path, path_size); U32 len = str_len(path); U32 i = len; #if OS_MACOS || OS_LINUX char delimeter = '/'; #elif OS_WINDOWS char delimeter = '\\'; #else #error Unknown OS: specify whether your OS uses forward or backward slashes for paths #endif 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 /* 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. */ #include "handmade_intrinsics.h" static void DrawBitmap(game_offscreen_buffer *Buffer, loaded_bitmap *Bitmap, F32 RelX, F32 RelY, S32 AlignX, S32 AlignY) { RelX -= (F32)AlignX; RelY -= (F32)AlignY; S32 MinX = RoundF32toS32(RelX); S32 MinY = RoundF32toS32(RelY); S32 MaxX = RoundF32toS32(RelX + (F32)Bitmap->Width); S32 MaxY = RoundF32toS32(RelY + (F32)Bitmap->Height); S32 Width = Buffer->Width; S32 Height = Buffer->Height; S32 SourceOffsetX = 0; if(MinX < 0) { SourceOffsetX = -MinX; MinX = 0; } S32 SourceOffsetY = 0; if(MinY < 0) { SourceOffsetY = -MinY; MinY = 0; } if(MaxX > Width) MaxX = Width; if(MaxY > Height) MaxY = Height; U32 *SourceRow = Bitmap->Pixels + Bitmap->Width*(Bitmap->Height - 1); SourceRow += -Bitmap->Width*SourceOffsetY + SourceOffsetX; U8 *DestRow = ((U8 *)Buffer->Memory + MinX*Buffer->BytesPerPixel + MinY*Buffer->Pitch); for(S32 Y = MinY; Y < MaxY; Y++) { U32 *Dest = (U32 *)DestRow; U32 *Source = SourceRow; for(S32 X = MinX; X < MaxX; X++) { F32 SA = (F32)((*Source >> 24) & 0xFF); F32 SR = (F32)((*Source >> 16) & 0xFF); F32 SG = (F32)((*Source >> 8) & 0xFF); F32 SB = (F32)((*Source >> 0) & 0xFF); F32 DR = (F32)((*Dest >> 16) & 0xFF); F32 DG = (F32)((*Dest >> 8) & 0xFF); F32 DB = (F32)((*Dest >> 0) & 0xFF); F32 A = SA / 255.0f; // TODO: This should be replaced by premultiplied alpha. F32 R = (1.0f-A)*DR + A*SR; F32 G = (1.0f-A)*DG + A*SG; F32 B = (1.0f-A)*DB + A*SB; *Dest = (((U32)RoundF32toS32(SA) << 24) | ((U32)RoundF32toS32(R) << 16) | ((U32)RoundF32toS32(G) << 8) | ((U32)RoundF32toS32(B) << 0)); Dest++; Source++; } DestRow += Buffer->Pitch; SourceRow -= Bitmap->Width; } } #pragma pack(push, 1) typedef struct bitmap_header { U16 FileType; U32 FileSize; U16 Reserved1; U16 Reserved2; U32 BitmapOffset; U32 Size; S32 Width; S32 Height; U16 Planes; U16 BitsPerPixel; U32 Compression; U32 SizeOfBitmap; S32 HorzResolution; S32 VertResolution; U32 ColorsUsed; U32 ColorsImportant; U32 RedMask; U32 GreenMask; U32 BlueMask; } bitmap_header; #pragma pack(pop) static loaded_bitmap DEBUGLoadBMPP(thread_context *Thread, debug_read_file_result (*DEBUGPlatformReadEntireFile)(thread_context *Thread, const char *), char *FileName) { loaded_bitmap Result; memset(&Result, 0, sizeof(Result)); debug_read_file_result ReadResult = DEBUGPlatformReadEntireFile(Thread, FileName); if(ReadResult.ContentsSize > 0) { bitmap_header *Header = (bitmap_header *)ReadResult.Contents; U32 *Pixels = (U32 *)((U8 *)ReadResult.Contents + Header->BitmapOffset); Result.Pixels = Pixels; Result.Width = Header->Width; Result.Height = Header->Height; ASSERT(Header->Compression == 3); // NOTE: The byte order in memory is determined by the Header. U32 RedMask = Header->RedMask; U32 GreenMask = Header->GreenMask; U32 BlueMask = Header->BlueMask; U32 AlphaMask = ~(RedMask | GreenMask | BlueMask); bit_scan_result RedShift = FindLeastSignificantSetBit(RedMask); bit_scan_result GreenShift = FindLeastSignificantSetBit(GreenMask); bit_scan_result BlueShift = FindLeastSignificantSetBit(BlueMask); bit_scan_result AlphaShift = FindLeastSignificantSetBit(AlphaMask); ASSERT(RedShift.Found); ASSERT(GreenShift.Found); ASSERT(BlueShift.Found); ASSERT(AlphaShift.Found); U32 *SourceDest = Pixels; for(S32 Y = 0; Y < Header->Height; Y++) { for(S32 X = 0; X < Header->Width; X++) { U32 C = *SourceDest; *SourceDest = ((((C >> AlphaShift.Index) & 0xFF) << 24) | (((C >> RedShift.Index ) & 0xFF) << 16) | (((C >> GreenShift.Index) & 0xFF) << 8) | (((C >> BlueShift.Index ) & 0xFF) << 0)); SourceDest++; } } } return Result; } void SDLSetProgramIcon(SDL_Window *Window, game_offscreen_buffer *ProgramIcon) { SDL_Surface *icon = SDL_CreateRGBSurfaceWithFormatFrom((void *)ProgramIcon->Memory, ProgramIcon->Width, ProgramIcon->Height, ProgramIcon->BytesPerPixel * 8, ProgramIcon->BytesPerPixel * ProgramIcon->Width, SDL_PIXELFORMAT_ARGB8888); if(icon) { SDL_SetWindowIcon(Window, icon); SDL_FreeSurface(icon); } else { /* TODO: This didn't work . . . SDL_GetError() */ } } #include "handmade_intrinsics.h" S32 main(S32 argc, char *argv[]) { sdl_state SDLState; 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: 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. */ /* NOTE: We create the window hidden at first. This is so that we do not get a window outline * that shows up whilst it is waiting for the rendered to be created. We want to display * the window once everything is ready. */ // TODO: SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, in non-debug build SDL_Window *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_GetCurrentDisplayMode(0, &display_mode); SDL_Renderer *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) { thread_context TempContext; // TODO: memset(&TempContext, 0, sizeof(TempContext)); loaded_bitmap ProgramIcon = DEBUGLoadBMPP(&TempContext, DEBUGPlatformReadEntireFile, "data/program_icon.bmp"); game_offscreen_buffer ProgramIconBuffer; ProgramIconBuffer.Memory = malloc((ProgramIcon.Width * ProgramIcon.Height) * 4); ProgramIconBuffer.Width = ProgramIcon.Width; ProgramIconBuffer.Height = ProgramIcon.Height; ProgramIconBuffer.BytesPerPixel = 4; ProgramIconBuffer.Pitch = ProgramIconBuffer.BytesPerPixel * ProgramIcon.Width; memset(ProgramIconBuffer.Memory, 0, (ProgramIcon.Width * ProgramIcon.Height) * ProgramIconBuffer.BytesPerPixel); DrawBitmap(&ProgramIconBuffer, &ProgramIcon, 0, 0, 0, 0); SDLSetProgramIcon(Window, &ProgramIconBuffer); // TODO: Make it a global. SDL_Cursor *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_SetWindowPosition(Window, (display_mode.w - RESOLUTION_WIDTH-20), (display_mode.h - RESOLUTION_HEIGHT-20)); GlobalRunning = TRUE; //sdl_window_dimension Dimension = SDLGetWindowDimension(Window); //SDLResizeTexture(&GlobalBackbuffer, Renderer, Dimension.Width, Dimension.Height); SDLResizeTexture(&GlobalBackbuffer, Renderer, RESOLUTION_WIDTH, RESOLUTION_HEIGHT); sdl_sound_output SoundOutput; memset(&SoundOutput, 0, sizeof(SoundOutput)); SoundOutput.SamplesPerSecond = 48000; SoundOutput.BytesPerSample = sizeof(S16) * 2; SoundOutput.SecondaryBufferSize = SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample; SoundOutput.LatencySampleCount = SoundOutput.SamplesPerSecond / 15; SDLInitSound(SoundOutput.SamplesPerSecond, SoundOutput.SamplesPerSecond * SoundOutput.BytesPerSample / 60); SDL_PauseAudio(0); // TODO: This should be paused until we have some actual sound to play. SoundOutput.Samples = calloc(SoundOutput.SamplesPerSecond, SoundOutput.BytesPerSample); /* SoundOutput.Samples = * malloc(SoundOutput.SecondaryBufferSize); */ /* NOTE: calloc auto clears to zero */ /* SDLClearSoundBuffer(&SoundOutput); */ #if BUILD_DEBUG void *BaseAddress = (void *)TB(2); #else void *BaseAddress = (void *)(0); #endif game_memory GameMemory; 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; #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 = (U8 *)(GameMemory.PermanentStorage) + GameMemory.PermanentStorageSize; if(SoundOutput.Samples && GameMemory.PermanentStorage && GameMemory.TransientStorage) { game_input Input[2]; U64 LastCounter; U64 LastCycleCount; S32 MonitorRefreshHz = SDLGetWindowRefreshRate(Window); /*GameUpdateHz = MonitorRefreshHz;*/ S32 GameUpdateHz = 30; /* NOTE: Temporarily target 30 FPS. */ F32 TargetSecondsPerFrame = 1.0f / (F32)GameUpdateHz; game_input *NewInput = &Input[0]; game_input *OldInput = &Input[1]; memset(Input, 0, sizeof(Input)); GlobalPerfCountFrequency = SDL_GetPerformanceFrequency(); sdl_game_code Game = SDLLoadGameCode(SDLState.DLLPath); SDL_ShowWindow(Window); // Everything is ready; display the window. U64 FPSLastCounter = SDLGetWallClock(); while(GlobalRunning) { 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(); U32 NewDLLWriteTime = GetLastWriteTime(SDLState.DLLPath); if(NewDLLWriteTime > Game.DLLLastWriteTime) { SDLUnloadGameCode(&Game); Game = SDLLoadGameCode(SDLState.DLLPath); } S32 MouseX, MouseY; SDL_GetGlobalMouseState(&MouseX, &MouseY); S32 WindowX, WindowY; SDL_GetWindowPosition(Window, &WindowX, &WindowY); NewInput->MouseX = MouseX - WindowX; NewInput->MouseY = MouseY - WindowY; U32 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); game_controller_input *OldKeyboardController = GetController(OldInput, 0); game_controller_input *NewKeyboardController = GetController(NewInput, 0); game_controller_input ZeroController; memset(&ZeroController, 0, sizeof(ZeroController)); *NewKeyboardController = ZeroController; NewKeyboardController->IsConnected = TRUE; for(U32 ButtonIndex = 0; ButtonIndex < ARRAY_SIZE(NewKeyboardController->Buttons); ButtonIndex++) { NewKeyboardController->Buttons[ButtonIndex].EndedDown = OldKeyboardController->Buttons[ButtonIndex].EndedDown; } SDLProcessMessages(&SDLState, NewKeyboardController); for(U32 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->IsAnalog = OldController->IsAnalog; 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; } F32 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; thread_context Context; memset(&Context, 0, sizeof(Context)); game_offscreen_buffer Buffer; 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); U64 WorkCounter = SDLGetWallClock(); F32 WorkSecondsElapsed = SDLGetSecondsElapsed(LastCounter, WorkCounter); F32 SecondsElapsedForFrame = WorkSecondsElapsed; if(SecondsElapsedForFrame < TargetSecondsPerFrame) { while(SecondsElapsedForFrame < TargetSecondsPerFrame) { SecondsElapsedForFrame = SDLGetSecondsElapsed(LastCounter, SDLGetWallClock()); if((TargetSecondsPerFrame - SecondsElapsedForFrame) >= 0.0f) SDL_Delay((U32)((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 = 1000.0 / MSPerFrame; LastCounter = EndCounter; #if 1 if(SDLGetSecondsElapsed(FPSLastCounter, SDLGetWallClock()) > 0.1f) { U64 EndCycleCount = __rdtsc(); U64 CyclesElapsed = EndCycleCount - LastCycleCount; F64 MCPF = ((F64)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(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; }