Files
handmadehero/sdl_handmade.c
T

1097 lines
41 KiB
C

#include "core.h"
// Remove SDL's entry point.
#if OS_WINDOWS
#define SDL_MAIN_HANDLED
#endif
#include "handmade.h"
#include "sdl_handmade.h"
#include <SDL.h>
#include <stdlib.h> /* malloc, calloc */
#include <string.h> /* memset, memcpy */
#include <stdio.h>
#include <sys/stat.h> /* fstat */
#include <fcntl.h> /* open */
#include <sys/stat.h> /* stat */
#if OS_MACOS || OS_LINUX
#include <dlfcn.h> /* dlopen */
#include <sys/mman.h> /* mmap */
#include <unistd.h> /* close, write, read, readlink, unlink */
#endif
#if OS_WINDOWS
#include <windows.h> /* VirtualAlloc */
#endif
#if OS_MACOS
#include <mach-o/dyld.h> /* _NSGetExecutablePath */
#endif
/* NOTE: This is so it compiles on ARM. */
#if ARCHITECTURE_X64 || ARCHITECTURE_X86
#if OS_MACOS || OS_LINUX
#include <x86intrin.h> // 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 A = (F32)((*Source >> 24) & 0xFF) / 255.0f;
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);
// 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(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++;
}
}
#if 0
U32 *Beginning = Pixels;
U32 *Ending = Pixels + (Header->Width * Header->Height);
for(S32 Y = 0; Y < Header->Height; Y++) {
for(S32 X = 0; X < Header->Width; X++) {
U32 Temp = *Beginning;
*Beginning = *Ending;
*Ending = Temp;
Beginning++;
Ending--;
}
}
#endif
}
return Result;
}
void SDLSetProgramIcon(SDL_Window *Window, game_offscreen_buffer *ProgramIcon)
{
U32 Rmask, Gmask, Bmask, Amask;
Rmask = 0x00ff0000;
Gmask = 0x0000ff00;
Bmask = 0x000000ff;
Amask = 0xff000000;
SDL_Surface *icon = SDL_CreateRGBSurfaceFrom((void *)ProgramIcon->Memory, ProgramIcon->Width, ProgramIcon->Height, ProgramIcon->BytesPerPixel * 8, ProgramIcon->BytesPerPixel * ProgramIcon->Width, Rmask, Gmask, Bmask, Amask);
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 = 4 * ProgramIcon.Width;
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;
}