diff options
Diffstat (limited to 'code/linux_handmade.cpp')
-rw-r--r-- | code/linux_handmade.cpp | 1489 |
1 files changed, 0 insertions, 1489 deletions
diff --git a/code/linux_handmade.cpp b/code/linux_handmade.cpp deleted file mode 100644 index 0ed3b1d..0000000 --- a/code/linux_handmade.cpp +++ /dev/null @@ -1,1489 +0,0 @@ -#include <stdio.h> -#include <string.h> -#include <x86intrin.h> -#include <dlfcn.h> -#include <fcntl.h> - -#include <unistd.h> -#include <dirent.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/wait.h> -#include <signal.h> -#include <linux/limits.h> -#include <linux/input.h> -#include <alsa/asoundlib.h> -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include <X11/keysymdef.h> -#include <X11/extensions/Xrandr.h> - -#include "handmade_platform.h" -#include "linux_handmade.h" - -#include "x11_keysym_convert.c" - -#define true 1 -#define false 0 - -#define ALSA_RECOVER_SILENT true -#define MAX_PLAYER_COUNT 4 - -#ifdef Assert -#undef Assert -#define Assert(Expression) \ -if(!(Expression)) \ -{ \ -raise(SIGTRAP); \ -} -#endif - -// NOTE(luca): Bits are layed out over multiple bytes. This macro checks which byte the bit will be set in. -#define IsEvdevBitSet(Bit, Array) (Array[(Bit) / 8] & (1 << ((Bit) % 8))) -#define BytesNeededForBits(Bits) ((Bits + 7) / 8) - -global_variable b32 GlobalRunning; -global_variable b32 GlobalPaused; - -void MemCpy(char *Dest, char *Source, size_t Count) -{ - while(Count--) *Dest++ = *Source++; -} - -void MemSet(char *Dest, char Value, size_t Count) -{ - while(Count--) *Dest++ = Value; -} - -int StrLen(char *String) -{ - size_t Result = 0; - - while(*String++) Result++; - - return Result; -} - -void CatStrings(size_t SourceACount, char *SourceA, - size_t SourceBCount, char *SourceB, - size_t DestCount, char *Dest) -{ - for(size_t Index = 0; - Index < SourceACount; - Index++) - { - *Dest++ = *SourceA++; - } - - for(size_t Index = 0; - Index < SourceBCount; - Index++) - { - *Dest++ = *SourceB++; - } -} - -internal rune -ConvertUTF8StringToRune(u8 UTF8String[4]) -{ - rune Codepoint = 0; - - if((UTF8String[0] & 0x80) == 0x00) - { - Codepoint = UTF8String[0]; - } - else if((UTF8String[0] & 0xE0) == 0xC0) - { - Codepoint = ( - ((UTF8String[0] & 0x1F) << 6*1) | - ((UTF8String[1] & 0x3F) << 6*0) - ); - } - else if((UTF8String[0] & 0xF0) == 0xE0) - { - Codepoint = ( - ((UTF8String[0] & 0x0F) << 6*2) | - ((UTF8String[1] & 0x3F) << 6*1) | - ((UTF8String[2] & 0x3F) << 6*0) - ); - } - else if((UTF8String[0] & 0xF8) == 0xF8) - { - Codepoint = ( - ((UTF8String[0] & 0x0E) << 6*3) | - ((UTF8String[1] & 0x3F) << 6*2) | - ((UTF8String[2] & 0x3F) << 6*1) | - ((UTF8String[3] & 0x3F) << 6*0) - ); - } - else - { - Assert(0); - } - - return Codepoint; -} - -struct linux_init_alsa_result -{ - snd_pcm_t *PCMHandle; - snd_pcm_hw_params_t *PCMParams; -}; - -internal linux_init_alsa_result LinuxInitALSA() -{ - linux_init_alsa_result Result = {}; - - int PCMResult = 0; - if((PCMResult = snd_pcm_open(&Result.PCMHandle, "default", - SND_PCM_STREAM_PLAYBACK, 0)) == 0) - { - snd_pcm_hw_params_alloca(&Result.PCMParams); - snd_pcm_hw_params_any(Result.PCMHandle, Result.PCMParams); - u32 ChannelCount = 2; - u32 SampleRate = 48000; - - if((PCMResult = snd_pcm_hw_params_set_access(Result.PCMHandle, Result.PCMParams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) - { - // TODO(luca): Logging - // snd_strerror(pcm) - } - if((PCMResult = snd_pcm_hw_params_set_format(Result.PCMHandle, Result.PCMParams, SND_PCM_FORMAT_S16_LE)) < 0) - { - // TODO(luca): Logging - } - if((PCMResult = snd_pcm_hw_params_set_channels(Result.PCMHandle, Result.PCMParams, ChannelCount)) < 0) - { - // TODO(luca): Logging - } - if((PCMResult = snd_pcm_hw_params_set_rate_near(Result.PCMHandle, Result.PCMParams, &SampleRate, 0)) < 0) - { - // TODO(luca): Logging - } - if((PCMResult = snd_pcm_nonblock(Result.PCMHandle, 1)) < 0) - { - // TODO(luca): Logging - } - if((PCMResult = snd_pcm_reset(Result.PCMHandle)) < 0) - { - // TODO(luca): Logging - } - if((PCMResult = snd_pcm_hw_params(Result.PCMHandle, Result.PCMParams)) < 0) - { - // TODO(luca): Logging - } - - } - else - { - // TODO(luca): Logging - } - - return Result; -} - -internal void LinuxGetAxisInfo(linux_gamepad *GamePad, linux_gamepad_axes_enum Axis, int AbsAxis) -{ - input_absinfo AxesInfo = {}; - if(ioctl(GamePad->File, EVIOCGABS(AbsAxis), &AxesInfo) != -1) - { - GamePad->Axes[Axis].Minimum = AxesInfo.minimum; - GamePad->Axes[Axis].Maximum = AxesInfo.maximum; - GamePad->Axes[Axis].Fuzz = AxesInfo.fuzz; - GamePad->Axes[Axis].Flat = AxesInfo.flat; - } -} - -internal void LinuxOpenGamePad(char *FilePath, linux_gamepad *GamePad) -{ - GamePad->File = open(FilePath, O_RDWR|O_NONBLOCK); - - if(GamePad->File != -1) - { - int Version = 0; - int IsCompatible = true; - - // TODO(luca): Check versions - ioctl(GamePad->File, EVIOCGVERSION, &Version); - ioctl(GamePad->File, EVIOCGNAME(sizeof(GamePad->Name)), GamePad->Name); - - char SupportedEventBits[BytesNeededForBits(EV_MAX)] = {}; - if(ioctl(GamePad->File, EVIOCGBIT(0, sizeof(SupportedEventBits)), SupportedEventBits) != -1) - { - if(!IsEvdevBitSet(EV_ABS, SupportedEventBits)) - { - // TODO(luca): Logging - IsCompatible = false; - } - if(!IsEvdevBitSet(EV_KEY, SupportedEventBits)) - { - // TODO(luca): Logging - IsCompatible = false; - } - } - - char SupportedKeyBits[BytesNeededForBits(KEY_MAX)] = {}; - if(ioctl(GamePad->File, EVIOCGBIT(EV_KEY , sizeof(SupportedKeyBits)), SupportedKeyBits) != -1) - { - if(!IsEvdevBitSet(BTN_GAMEPAD, SupportedKeyBits)) - { - // TODO(luca): Logging - IsCompatible = false; - } - } - - GamePad->SupportsRumble = IsEvdevBitSet(EV_FF, SupportedEventBits); - - if(IsCompatible) - { - // NOTE(luca): Map evdev axes to my enum. - LinuxGetAxisInfo(GamePad, LSTICKX, ABS_X); - LinuxGetAxisInfo(GamePad, LSTICKY, ABS_Y); - LinuxGetAxisInfo(GamePad, RSTICKX, ABS_RX); - LinuxGetAxisInfo(GamePad, RSTICKY, ABS_RY); - LinuxGetAxisInfo(GamePad, LSHOULDER, ABS_Z); - LinuxGetAxisInfo(GamePad, RSHOULDER, ABS_RZ); - LinuxGetAxisInfo(GamePad, DPADX, ABS_HAT0X); - LinuxGetAxisInfo(GamePad, DPADY, ABS_HAT0Y); - - MemCpy(GamePad->FilePath, FilePath, StrLen(FilePath)); - } - else - { - close(GamePad->File); - *GamePad = {}; - GamePad->File = -1; - } - } -} - -// TODO(luca): Make this work in the case of multiple displays. -internal r32 LinuxGetMonitorRefreshRate(Display *DisplayHandle, Window RootWindow) -{ - r32 Result = 0; - - void *LibraryHandle = dlopen("libXrandr.so.2", RTLD_NOW); - - if(LibraryHandle) - { - typedef XRRScreenResources *xrr_get_screen_resources(Display *Display, Window Window); - typedef XRRCrtcInfo *xrr_get_crtc_info(Display* Display, XRRScreenResources *Resources, RRCrtc Crtc); - - xrr_get_screen_resources *XRRGetScreenResources = (xrr_get_screen_resources *)dlsym(LibraryHandle, "XRRGetScreenResources"); - xrr_get_crtc_info *XRRGetCrtcInfo = (xrr_get_crtc_info *)dlsym(LibraryHandle, "XRRGetCrtcInfo"); - - XRRScreenResources *ScreenResources = XRRGetScreenResources(DisplayHandle, RootWindow); - - RRMode ActiveModeID = 0; - for(int CRTCIndex = 0; - CRTCIndex< ScreenResources->ncrtc; - CRTCIndex++) - { - XRRCrtcInfo *CRTCInfo = XRRGetCrtcInfo(DisplayHandle, ScreenResources, ScreenResources->crtcs[CRTCIndex]); - if(CRTCInfo->mode) - { - ActiveModeID = CRTCInfo->mode; - } - } - - r32 ActiveRate = 0; - for(int ModeIndex = 0; - ModeIndex < ScreenResources->nmode; - ModeIndex++) - { - XRRModeInfo ModeInfo = ScreenResources->modes[ModeIndex]; - if(ModeInfo.id == ActiveModeID) - { - Assert(ActiveRate == 0); - Result = (r32)ModeInfo.dotClock / ((r32)ModeInfo.hTotal * (r32)ModeInfo.vTotal); - } - } - - dlclose(LibraryHandle); - } - - return Result; -} - -internal void LinuxBuildFileNameFromExecutable(char *Dest, linux_state *State, char *FileName) -{ - char *Path = State->ExecutablePath; - - size_t LastSlash = 0; - for(char *Scan = Path; - *Scan; - Scan++) - { - if(*Scan == '/') - { - LastSlash = Scan - Path; - } - } - - for(size_t Index = 0; - Index < LastSlash + 1; - Index++) - { - *Dest++ = *Path++; - } - - while(*FileName) - *Dest++ = *FileName++; -} - -GAME_UPDATE_AND_RENDER(LinuxGameUpdateAndRenderStub) {} -GAME_GET_SOUND_SAMPLES(LinuxGameGetSoundSamplesStub) {} - -internal linux_game_code LinuxLoadGameCode(char *LibraryPath) -{ - linux_game_code Result = {}; - - Result.LibraryHandle = dlopen(LibraryPath, RTLD_NOW); - if(Result.LibraryHandle) - { - Result.UpdateAndRender = (game_update_and_render *)dlsym(Result.LibraryHandle, "GameUpdateAndRender"); - Result.GetSoundSamples = (game_get_sound_samples *)dlsym(Result.LibraryHandle, "GameGetSoundSamples"); - } - else - { - Result.UpdateAndRender = (game_update_and_render *)LinuxGameUpdateAndRenderStub; - Result.GetSoundSamples = (game_get_sound_samples *)LinuxGameGetSoundSamplesStub; - } - - return Result; -} - -internal void LinuxUnloadGameCode(linux_game_code *Game) -{ - if(Game->LibraryHandle) - { - dlclose(Game->LibraryHandle); - } -} - -internal void LinuxProcessKeyPress(game_button_state *ButtonState, b32 IsDown) -{ - if(ButtonState->EndedDown != IsDown) - { - ButtonState->EndedDown = IsDown; - ButtonState->HalfTransitionCount++; - } -} - -internal void LinuxBeginRecordingInput(linux_state *State, int SlotIndex) -{ - char FileName[64]; - sprintf(FileName, "loop_edit_%d.hmi", SlotIndex); - - int File = open(FileName, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600); - if(State->InputRecordingFile != -1) - { - int BytesWritten = write(File, State->GameMemoryBlock, State->TotalSize); - if(BytesWritten != -1 && - BytesWritten == (int)State->TotalSize) - { - State->InputRecordingFile = File; - State->InputRecordingIndex = SlotIndex; - } - else - { - // TODO(luca): Logging - } - } - else - { - // TODO(luca): Logging - } - -} - -internal void LinuxEndRecordingInput(linux_state *State, int SlotIndex) -{ - Assert(State->InputRecordingIndex); - Assert(State->InputRecordingFile != -1); - - State->InputRecordingIndex = 0; - - if(close(State->InputRecordingFile) == -1) - { - State->InputRecordingFile = -1; - } - else - { - // TODO(luca): Logging - } - -} - -internal void LinuxBeginInputPlayBack(linux_state *State, int SlotIndex) -{ - char FileName[64]; - sprintf(FileName, "loop_edit_%d.hmi", SlotIndex); - - int File = open(FileName, O_RDONLY, 0400); - if(File != -1) - { - State->InputPlayingFile = File; - - int BytesRead = read(State->InputPlayingFile, State->GameMemoryBlock, State->TotalSize); - if(BytesRead != -1) - { - State->InputPlayingIndex = SlotIndex; - } - else - { - // TODO(luca): Logging - } - } - else - { - // TODO(luca): Logging - } - -} - -internal void LinuxEndInputPlayBack(linux_state *State) -{ - Assert(State->InputPlayingIndex); - Assert(State->InputPlayingFile != -1); - - if(close(State->InputPlayingFile) != -1) - { - State->InputPlayingFile = -1; - } - else - { - // TODO(luca): Logging - } - - State->InputPlayingIndex = 0; -} - -internal void LinuxRecordInput(linux_state *State, game_input *Input) -{ - int BytesWritten = write(State->InputRecordingFile, Input, sizeof(*Input)); - if(BytesWritten != -1 && - BytesWritten == (int)sizeof(*Input)) - { - } - else - { - // TODO: Logging - } - -} - -internal void LinuxPlayBackInput(linux_state *State, game_input *Input) -{ - int BytesRead = read(State->InputPlayingFile, Input, sizeof(*Input)); - if(BytesRead != -1) - { - if(BytesRead == 0) - { - int PlayingIndex = State->InputPlayingIndex; - LinuxEndInputPlayBack(State); - LinuxBeginInputPlayBack(State, PlayingIndex); - read(State->InputPlayingFile, Input, sizeof(*Input)); - } - } - -} - -internal void LinuxHideCursor(Display *DisplayHandle, Window WindowHandle) -{ - XColor black = {}; - char NoData[8] = {}; - - Pixmap BitmapNoData = XCreateBitmapFromData(DisplayHandle, WindowHandle, NoData, 8, 8); - Cursor InvisibleCursor = XCreatePixmapCursor(DisplayHandle, BitmapNoData, BitmapNoData, - &black, &black, 0, 0); - XDefineCursor(DisplayHandle, WindowHandle, InvisibleCursor); - XFreeCursor(DisplayHandle, InvisibleCursor); - XFreePixmap(DisplayHandle, BitmapNoData); -} - -internal void LinuxShowCursor(Display *DisplayHandle, Window WindowHandle) -{ - XUndefineCursor(DisplayHandle, WindowHandle); -} - -internal void LinuxProcessPendingMessages(Display *DisplayHandle, Window WindowHandle, - XIC InputContext, Atom WM_DELETE_WINDOW, linux_state *State, game_controller_input *KeyboardController) -{ - XEvent WindowEvent = {}; - while(XPending(DisplayHandle) > 0) - { - XNextEvent(DisplayHandle, &WindowEvent); - b32 FilteredEvent = XFilterEvent(&WindowEvent, WindowHandle); - if(FilteredEvent) - { - Assert(WindowEvent.type == KeyPress || WindowEvent.type == KeyRelease); - } - - switch(WindowEvent.type) - { - case KeyPress: - case KeyRelease: - { - //- How text input works - // The needs: - // 1. Preserve game buttons, so that we can switch between a "game mode" or - // "text input mode". - // 2. Text input using the input method of the user which should allow for utf8 characters. - // 3. Hotkey support. Eg. quickly navigating text. - // 3 will be supported by 2 for code reuse. - // - // We are going to send a buffer text button presses to the game layer, this solves these - // issues: - // - Pressing the same key multiple times in one frame. - // - Having modifiers be specific to each key press. - // - Not having to create a button record for each possible character in the structure. - // - Key events come in one at a time in the event loop, thus we need to have a buffer for - // multiple keys pressed on a single frame. - // - // We store a count along the buffer and in the buffer we store the utf8 codepoint and its - // modifiers. - // The app code is responsible for traversing this buffer and applying the logic. - - // The problem of input methods and hotkeys: - // Basically the problem is that if we allow the input method and combo's that could be - // filtered by the input method it won't seem consistent to the user. - // So we don't allow key bound to the input method to have an effect and we only pass key - // inputs that have not been filtered. - // - // In the platform layer we handle the special case were the input methods creates non- - // printable characters and we decompose those key inputs since non-printable characters - // have no use anymore. - - // Extra: - // - I refuse to check which keys bind to what modifiers. It's not important. - - // - Handy resources: - // - https://www.coderstool.com/unicode-text-converter - // - man Compose(5). - // - https://en.wikipedia.org/wiki/Control_key#History - - KeySym Symbol = XLookupKeysym(&WindowEvent.xkey, 0); - b32 IsDown = (WindowEvent.type == KeyPress); - - // TODO(luca): Refresh mappings. - // NOTE(luca): Only KeyPress events see man page of Xutf8LookupString(). And skip filtered events for text input, but keep them for controller. - if(IsDown && !FilteredEvent) - { - rune Codepoint = 'e'; - u8 LookupBuffer[4] = {}; - Status LookupStatus = {}; - - s32 BytesLookepdUp = Xutf8LookupString(InputContext, &WindowEvent.xkey, - (char *)&LookupBuffer, ArrayCount(LookupBuffer), - 0, &LookupStatus); - Assert(LookupStatus != XBufferOverflow); - Assert(BytesLookepdUp <= 4); - - if(LookupStatus!= XLookupNone && - LookupStatus!= XLookupKeySym) - { - if(BytesLookepdUp) - { - Assert(KeyboardController->Keyboard.TextInputCount < ArrayCount(KeyboardController->Keyboard.TextInputBuffer)); - - Codepoint = ConvertUTF8StringToRune(LookupBuffer); - - // NOTE(luca): Input methods might produce non printable characters (< ' '). If this - // happens we try to "decompose" the key input. - if(Codepoint < ' ' && Codepoint >= 0) - { - if(Symbol >= XK_space) - { - Codepoint = (char)(' ' + (Symbol - XK_space)); - } - } - - - if(Codepoint >= ' ' || Codepoint < 0) - { - game_text_button *TextButton = &KeyboardController->Keyboard.TextInputBuffer[KeyboardController->Keyboard.TextInputCount++]; - TextButton->Codepoint = Codepoint; - TextButton->Shift = (WindowEvent.xkey.state & ShiftMask); - TextButton->Control = (WindowEvent.xkey.state & ControlMask); - TextButton->Alt = (WindowEvent.xkey.state & Mod1Mask); -#if 0 - printf("%d bytes '%c' %d (%c|%c|%c)\n", - BytesLookepdUp, - ((Codepoint >= ' ') ? (char)Codepoint : '\0'), - Codepoint, - ((WindowEvent.xkey.state & ShiftMask) ? 'S' : ' '), - ((WindowEvent.xkey.state & ControlMask) ? 'C' : ' '), - ((WindowEvent.xkey.state & Mod1Mask) ? 'A' : ' ')); -#endif - } - else - { - // TODO(luca): Logging - } - - } - } - } - - if(0) {} - else if(Symbol == XK_w) - { - LinuxProcessKeyPress(&KeyboardController->MoveUp, IsDown); - } - else if(Symbol == XK_a) - { - LinuxProcessKeyPress(&KeyboardController->MoveLeft, IsDown); - } - else if(Symbol == XK_r) - { - LinuxProcessKeyPress(&KeyboardController->MoveDown, IsDown); - } - else if(Symbol == XK_s) - { - LinuxProcessKeyPress(&KeyboardController->MoveRight, IsDown); - } - else if(Symbol == XK_Up) - { - LinuxProcessKeyPress(&KeyboardController->ActionUp, IsDown); - } - else if(Symbol == XK_Left) - { - LinuxProcessKeyPress(&KeyboardController->ActionLeft, IsDown); - } - else if(Symbol == XK_Down) - { - LinuxProcessKeyPress(&KeyboardController->ActionDown, IsDown); - } - else if(Symbol == XK_Right) - { - LinuxProcessKeyPress(&KeyboardController->ActionRight, IsDown); - } - else if(Symbol == XK_space) - { - LinuxProcessKeyPress(&KeyboardController->Start, IsDown); - } - else if((WindowEvent.xkey.state & Mod1Mask) && - (Symbol == XK_p)) - { - if(IsDown) - { - GlobalPaused = !GlobalPaused; - } - } - else if((WindowEvent.xkey.state & Mod1Mask) && - (Symbol == XK_l)) - { - if(IsDown) - { - if(State->InputPlayingIndex == 0) - { - if(State->InputRecordingIndex == 0) - { - LinuxBeginRecordingInput(State, 1); - } - else - { - LinuxEndRecordingInput(State, 1); - LinuxBeginInputPlayBack(State, 1); - } - } - else - { - // NOTE(luca) Reset buttons so they aren't held. - // TODO(luca): Check if still needed since we clear halftransition counts. - for(u32 ButtonIndex = 0; - ButtonIndex < ArrayCount(KeyboardController->Buttons); - ButtonIndex++) - { - KeyboardController->Buttons[ButtonIndex] = {}; - } - LinuxEndInputPlayBack(State); - } - } - } - else if((WindowEvent.xkey.state & Mod1Mask) && - (Symbol == XK_F4)) - { - GlobalRunning = false; - } - - } break; - case DestroyNotify: - { - XDestroyWindowEvent *Event = (XDestroyWindowEvent *)&WindowEvent; - if(Event->window == WindowHandle) - { - GlobalRunning = false; - } - } break; - - case ClientMessage: - { - XClientMessageEvent *Event = (XClientMessageEvent *)&WindowEvent; - if((Atom)Event->data.l[0] == WM_DELETE_WINDOW) - { - XDestroyWindow(DisplayHandle, WindowHandle); - GlobalRunning = false; - } - } break; - - case EnterNotify: - { - //LinuxHideCursor(DisplayHandle, WindowHandle); - } break; - - case LeaveNotify: - { - //LinuxShowCursor(DisplayHandle, WindowHandle); - } break; - } - } -} - -internal void LinuxSetSizeHint(Display *DisplayHandle, Window WindowHandle, - int MinWidth, int MinHeight, - int MaxWidth, int MaxHeight) -{ - XSizeHints Hints = {}; - if(MinWidth > 0 && MinHeight > 0) Hints.flags |= PMinSize; - if(MaxWidth > 0 && MaxHeight > 0) Hints.flags |= PMaxSize; - - Hints.min_width = MinWidth; - Hints.min_height = MinHeight; - Hints.max_width = MaxWidth; - Hints.max_height = MaxHeight; - - XSetWMNormalHints(DisplayHandle, WindowHandle, &Hints); -} - -DEBUG_PLATFORM_READ_ENTIRE_FILE(DEBUGPlatformReadEntireFile) -{ - debug_read_file_result Result = {}; - - int File = open(FileName, O_RDONLY); - if(File != -1) - { - struct stat FileStats = {}; - fstat(File, &FileStats); - Result.ContentsSize = FileStats.st_size; - Result.Contents = mmap(0, FileStats.st_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, File, 0); - - close(File); - } - - return Result; -} - -PLATFORM_RUN_COMMAND_AND_GET_OUTPUT(PlatformRunCommandAndGetOutput) -{ - int Result = 0; - - int HandlesLink[2] = {0}; - int WaitStatus = 0; - pid_t ChildPID = 0; - int Ret = 0; - - char *FilePath = Command[0]; - int AccessMode = F_OK | X_OK; - Ret = access(FilePath, AccessMode); - - if(Ret == 0) - { - Ret = pipe(HandlesLink); - if(Ret != -1) - { - ChildPID = fork(); - if(ChildPID != -1) - { - if(ChildPID == 0) - { - dup2(HandlesLink[1], STDOUT_FILENO); - execve(Command[0], Command, 0); - } - else - { - wait(&WaitStatus); - - Result = read(HandlesLink[0], OutputBuffer, 4096); - if(Result == -1) - { - Result = 0; - } - } - - } - else - { - // TODO: Logging - } - } - else - { - // TODO: Logging - } - } - else - { - // TODO: Logging - } - - return Result; -} - -DEBUG_PLATFORM_FREE_FILE_MEMORY(DEBUGPlatformFreeFileMemory) -{ - munmap(Memory, MemorySize); -} - -DEBUG_PLATFORM_WRITE_ENTIRE_FILE(DEBUGPlatformWriteEntireFile) -{ - b32 Result = false; - - int FD = open(FileName, O_CREAT|O_WRONLY|O_TRUNC, 00600); - if(FD != -1) - { - if(write(FD, Memory, MemorySize) != MemorySize) - { - Result = true; - } - - close(FD); - } - - return Result; -} - -internal struct timespec LinuxGetLastWriteTime(char *FilePath) -{ - struct timespec Result = {}; - - struct stat LibraryFileStats = {}; - if(!stat(FilePath, &LibraryFileStats)) - { - Result = LibraryFileStats.st_mtim; - } - - return Result; -} - -internal struct timespec LinuxGetWallClock() -{ - struct timespec Counter = {}; - clock_gettime(CLOCK_MONOTONIC, &Counter); - return Counter; -} - -internal s64 LinuxGetNSecondsElapsed(struct timespec Start, struct timespec End) -{ - s64 Result = 0; - Result = ((s64)End.tv_sec*1000000000 + (s64)End.tv_nsec) - ((s64)Start.tv_sec*1000000000 + (s64)Start.tv_nsec); - return Result; -} - -internal r32 LinuxGetSecondsElapsed(struct timespec Start, struct timespec End) -{ - r32 Result = 0; - Result = LinuxGetNSecondsElapsed(Start, End)/1000.0f/1000.0f/1000.0f; - - return Result; -} - -internal r32 LinuxNormalizeAxisValue(s32 Value, linux_gamepad_axis Axis) -{ - r32 Result = 0; - if(Value) - { - // ((value - min / max) - 0.5) * 2 - r32 Normalized = ((r32)((r32)(Value - Axis.Minimum) / (r32)(Axis.Maximum - Axis.Minimum)) - 0.5f)*2; - Result = Normalized; - } - Assert(Result <= 1.0f && Result >= -1.0f); - - return Result; -} - -void LinuxDebugVerticalLine(game_offscreen_buffer *Buffer, int X, int Y, u32 Color) -{ - int Height = 32; - - if(X <= Buffer->Width && X >= 0 && - Y <= Buffer->Height - Height && Y <= 0) - { - u8 *Row = (u8 *)Buffer->Memory + Y*Buffer->Pitch + X*Buffer->BytesPerPixel; - while(Height--) - { - *(u32 *)Row = Color; - Row += Buffer->Pitch; - } - } -} - -int main(int ArgC, char *Args[]) -{ - // NOTE(luca): Change to executable's current directory such that file paths are relative to that in the game code. - char *ExePath = Args[0]; - int Length = StrLen(ExePath); - char ExecutableDirPath[Length]; - int LastSlash = 0; - for(int At = 0; - At < Length; - At++) - { - if(ExePath[At] == '/') - { - LastSlash = At; - } - } - MemCpy(ExecutableDirPath, ExePath, LastSlash); - ExecutableDirPath[LastSlash] = 0; - if(chdir(ExecutableDirPath) == -1) - { - // TODO(luca): Logging - } - - Display *DisplayHandle = XOpenDisplay(0); - - if(DisplayHandle) - { - Window RootWindow = XDefaultRootWindow(DisplayHandle); - int Screen = XDefaultScreen(DisplayHandle); - int Width = 1920/2; - int Height = 1080/2; - int ScreenBitDepth = 24; - XVisualInfo WindowVisualInfo = {}; - if(XMatchVisualInfo(DisplayHandle, Screen, ScreenBitDepth, TrueColor, &WindowVisualInfo)) - { - XSetWindowAttributes WindowAttributes = {}; - WindowAttributes.bit_gravity = StaticGravity; -#if HANDMADE_INTERNAL - WindowAttributes.background_pixel = 0xFF00FF; -#endif - WindowAttributes.colormap = XCreateColormap(DisplayHandle, RootWindow, WindowVisualInfo.visual, AllocNone); - WindowAttributes.event_mask = (StructureNotifyMask | - KeyPressMask | KeyReleaseMask | - EnterWindowMask | LeaveWindowMask); - u64 WindowAttributeMask = CWBitGravity | CWBackPixel | CWColormap | CWEventMask; - - Window WindowHandle = XCreateWindow(DisplayHandle, RootWindow, - 1920 - Width - 10, 10, - Width, Height, - 0, - WindowVisualInfo.depth, InputOutput, - WindowVisualInfo.visual, WindowAttributeMask, &WindowAttributes); - if(WindowHandle) - { - XStoreName(DisplayHandle, WindowHandle, "Handmade Window"); - LinuxSetSizeHint(DisplayHandle, WindowHandle, Width, Height, Width, Height); - - Atom WM_DELETE_WINDOW = XInternAtom(DisplayHandle, "WM_DELETE_WINDOW", False); - if(!XSetWMProtocols(DisplayHandle, WindowHandle, &WM_DELETE_WINDOW, 1)) - { - // TODO(luca): Logging - } - - XClassHint ClassHint = {}; - ClassHint.res_name = "Handmade Window"; - ClassHint.res_class = "Handmade Window"; - XSetClassHint(DisplayHandle, WindowHandle, &ClassHint); - - XSetLocaleModifiers(""); - - XIM InputMethod = XOpenIM(DisplayHandle, 0, 0, 0); - if(!InputMethod){ - XSetLocaleModifiers("@im=none"); - InputMethod = XOpenIM(DisplayHandle, 0, 0, 0); - } - XIC InputContext = XCreateIC(InputMethod, - XNInputStyle, XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, WindowHandle, - XNFocusWindow, WindowHandle, - NULL); - XSetICFocus(InputContext); - - int BitsPerPixel = 32; - int BytesPerPixel = BitsPerPixel/8; - int WindowBufferSize = Width*Height*BytesPerPixel; - char *WindowBuffer = (char *)malloc(WindowBufferSize); - - XImage *WindowImage = XCreateImage(DisplayHandle, WindowVisualInfo.visual, WindowVisualInfo.depth, ZPixmap, 0, WindowBuffer, Width, Height, BitsPerPixel, 0); - GC DefaultGC = DefaultGC(DisplayHandle, Screen); - - linux_state LinuxState = {}; - MemCpy(LinuxState.ExecutablePath, Args[0], strlen(Args[0])); - - char LibraryFullPath[] = "handmade.so"; - linux_game_code Game = LinuxLoadGameCode(LibraryFullPath); - Game.LibraryLastWriteTime = LinuxGetLastWriteTime(LibraryFullPath); - - game_memory GameMemory = {}; - - GameMemory.PermanentStorageSize = Megabytes(64); - GameMemory.TransientStorageSize = Gigabytes(1); - GameMemory.DEBUGPlatformReadEntireFile = DEBUGPlatformReadEntireFile; - GameMemory.DEBUGPlatformFreeFileMemory = DEBUGPlatformFreeFileMemory; - GameMemory.DEBUGPlatformWriteEntireFile = DEBUGPlatformWriteEntireFile; - GameMemory.PlatformRunCommandAndGetOutput = PlatformRunCommandAndGetOutput; - -#if HANDMADE_INTERNAL - void *BaseAddress = (void *)Terabytes(2); -#else - void *BaseAddress = 0; -#endif - // TODO(casey): TransientStorage needs to be broken into game transient and cache transient - LinuxState.TotalSize = GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize; - LinuxState.GameMemoryBlock = mmap(BaseAddress, LinuxState.TotalSize, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0); - GameMemory.PermanentStorage = LinuxState.GameMemoryBlock; - GameMemory.TransientStorage = (u8 *)GameMemory.PermanentStorage + GameMemory.PermanentStorageSize; - - game_input Input[2] = {}; - game_input *NewInput = &Input[0]; - game_input *OldInput = &Input[1]; - - linux_gamepad GamePads[MAX_PLAYER_COUNT] = {}; - for(int GamePadIndex = 0; - GamePadIndex < MAX_PLAYER_COUNT; - GamePadIndex++) - { - GamePads[GamePadIndex].File = -1; - } - - char EventDirectoryName[] = "/dev/input/"; - DIR *EventDirectory = opendir(EventDirectoryName); - struct dirent *Entry = 0; - int GamePadIndex = 0; - while((Entry = readdir(EventDirectory))) - { - if(!strncmp(Entry->d_name, "event", sizeof("event") - 1)) - { - char FilePath[PATH_MAX] = {}; - CatStrings(sizeof(EventDirectoryName) - 1, EventDirectoryName, - StrLen(Entry->d_name), Entry->d_name, - sizeof(FilePath) - 1, FilePath); - if(GamePadIndex < MAX_PLAYER_COUNT) - { - linux_gamepad *GamePadAt = &GamePads[GamePadIndex]; - LinuxOpenGamePad(FilePath, GamePadAt); - if(GamePadAt->File != -1) - { -#if 0 - for(int AxesIndex = 0; - AxesIndex < AXES_COUNT; - AxesIndex++) - { - linux_gamepad_axis Axis = GamePadAt->Axes[AxesIndex]; - printf("min: %d, max: %d, fuzz: %d, flat: %d\n", Axis.Minimum, Axis.Maximum, Axis.Fuzz, Axis.Flat); - } -#endif - GamePadIndex++; - } - } - else - { - // TODO(luca): Logging - } - } - } - - game_offscreen_buffer OffscreenBuffer = {}; - OffscreenBuffer.Memory = WindowBuffer; - OffscreenBuffer.Width = Width; - OffscreenBuffer.Height = Height; - OffscreenBuffer.BytesPerPixel = BytesPerPixel; - OffscreenBuffer.Pitch = Width*BytesPerPixel; - - int LastFramesWritten = 0; - unsigned int SampleRate, ChannelCount, PeriodTime, SampleCount; - snd_pcm_status_t *PCMStatus = 0; - snd_pcm_t* PCMHandle = 0; - snd_pcm_hw_params_t *PCMParams = 0; - snd_pcm_uframes_t PeriodSize = 0; - snd_pcm_uframes_t PCMBufferSize = 0; - - linux_init_alsa_result ALSAInit = LinuxInitALSA(); - PCMHandle = ALSAInit.PCMHandle; - PCMParams = ALSAInit.PCMParams; - snd_pcm_hw_params_get_channels(PCMParams, &ChannelCount); - snd_pcm_hw_params_get_rate(PCMParams, &SampleRate, 0); - snd_pcm_hw_params_get_period_size(PCMParams, &PeriodSize, 0); - snd_pcm_hw_params_get_period_time(PCMParams, &PeriodTime, NULL); - snd_pcm_hw_params_get_buffer_size(PCMParams, &PCMBufferSize); - snd_pcm_status_malloc(&PCMStatus); - -#if 0 - { - Assert(0); - u32 Value, Result; - snd_pcm_uframes_t Frames; - Result = snd_pcm_hw_params_get_buffer_time_min(PCMParams, &Value, 0); - Result = snd_pcm_hw_params_get_buffer_size_min(PCMParams, &Frames); - Frames = PCMBufferSize/2; - Result = snd_pcm_hw_params_set_period_size_near(PCMHandle, PCMParams, &Frames, 0); - Result = snd_pcm_hw_params_get_period_size(PCMParams, &PeriodSize, 0); - } -#endif - - - char AudioSamples[PCMBufferSize]; - u64 Periods = 2; - u32 BytesPerSample = (sizeof(s16)*ChannelCount); - -#if 1 - r32 GameUpdateHz = 60; -#else - r32 GameUpdateHz = LinuxGetMonitorRefreshRate(DisplayHandle, RootWindow); -#endif - - thread_context ThreadContext = {}; - - XMapWindow(DisplayHandle, WindowHandle); - XFlush(DisplayHandle); - - struct timespec LastCounter = LinuxGetWallClock(); - struct timespec FlipWallClock = LinuxGetWallClock(); - r32 TargetSecondsPerFrame = 1.0f / GameUpdateHz; - - GlobalRunning = true; - - u64 LastCycleCount = __rdtsc(); - while(GlobalRunning) - { - NewInput->dtForFrame = TargetSecondsPerFrame; - -#if HANDMADE_SLOW - // NOTE(luca): Because gcc will first create an empty file and then write into it we skip trying to reload when the file is empty. - struct stat FileStats = {}; - stat(LibraryFullPath, &FileStats); - if(FileStats.st_size) - { - s64 SecondsElapsed = LinuxGetNSecondsElapsed(Game.LibraryLastWriteTime, FileStats.st_mtim) / 1000/1000; - if(SecondsElapsed > 0) - { - LinuxUnloadGameCode(&Game); - Game = LinuxLoadGameCode(LibraryFullPath); - Game.LibraryLastWriteTime = FileStats.st_mtim; - } - } -#endif - - game_controller_input *OldKeyboardController = GetController(OldInput, 0); - game_controller_input *NewKeyboardController = GetController(NewInput, 0); - NewKeyboardController->IsConnected = true; - - NewKeyboardController->Keyboard.TextInputCount = 0; - for(u32 ButtonIndex = 0; - ButtonIndex < ArrayCount(NewKeyboardController->Buttons); - ButtonIndex++) - { - NewKeyboardController->Buttons[ButtonIndex].HalfTransitionCount = 0; - } - - LinuxProcessPendingMessages(DisplayHandle, WindowHandle, InputContext, WM_DELETE_WINDOW, - &LinuxState, NewKeyboardController); - - // TODO(luca): Use buttonpress/release events instead so we query this less frequently. - s32 MouseX = 0, MouseY = 0, MouseZ = 0; // TODO(luca): Support mousewheel? - u32 MouseMask = 0; - u64 Ignored; - if(XQueryPointer(DisplayHandle, WindowHandle, - &Ignored, &Ignored, (int *)&Ignored, (int *)&Ignored, - &MouseX, &MouseY, &MouseMask)) - { - if(MouseX <= OffscreenBuffer.Width && - MouseX >= 0 && - MouseY <= OffscreenBuffer.Height && - MouseY >= 0) - { - NewInput->MouseY = MouseY; - NewInput->MouseX = MouseX; - - for(u32 ButtonIndex = 0; - ButtonIndex < PlatformMouseButton_Count; - ButtonIndex++) - { - NewInput->MouseButtons[ButtonIndex].EndedDown = OldInput->MouseButtons[ButtonIndex].EndedDown; - NewInput->MouseButtons[ButtonIndex].HalfTransitionCount = 0; - } - - LinuxProcessKeyPress(&NewInput->MouseButtons[PlatformMouseButton_Left], (MouseMask & Button1Mask)); - LinuxProcessKeyPress(&NewInput->MouseButtons[PlatformMouseButton_Middle], (MouseMask & Button2Mask)); - LinuxProcessKeyPress(&NewInput->MouseButtons[PlatformMouseButton_Right], (MouseMask & Button3Mask)); - } - } - - for(int GamePadIndex = 0; - GamePadIndex < MAX_PLAYER_COUNT; - GamePadIndex++) - { - linux_gamepad *GamePadAt = &GamePads[GamePadIndex]; - - if(GamePadAt->File != -1) - { - game_controller_input *OldController = GetController(OldInput, 0); - game_controller_input *NewController = GetController(NewInput, 0); - - // TODO(luca): Cross frame values!!! - struct input_event InputEvents[64] = {}; - int BytesRead = 0; - - BytesRead = read(GamePadAt->File, InputEvents, sizeof(InputEvents)); - if(BytesRead != -1) - { - for(u32 EventIndex = 0; - EventIndex < ArrayCount(InputEvents); - EventIndex++) - { - struct input_event EventAt = InputEvents[EventIndex]; - - switch(EventAt.type) - { - case EV_KEY: - { - b32 IsDown = EventAt.value; - if(0) {} - else if(EventAt.code == BTN_A) - { - NewController->ActionDown.EndedDown = IsDown; - } - else if(EventAt.code == BTN_B) - { - NewController->ActionRight.EndedDown = IsDown; - } - else if(EventAt.code == BTN_X) - { - NewController->ActionLeft.EndedDown = IsDown; - } - else if(EventAt.code == BTN_Y) - { - NewController->ActionUp.EndedDown = IsDown; - } - else if(EventAt.code == BTN_START) - { - NewController->Start.EndedDown = IsDown; - } - else if(EventAt.code == BTN_BACK) - { - NewController->Back.EndedDown = IsDown; - } - } break; - - case EV_ABS: - { - if(0) {} - else if(EventAt.code == ABS_X) - { - NewController->IsAnalog = true; - - NewController->StickAverageX = LinuxNormalizeAxisValue(EventAt.value, GamePadAt->Axes[LSTICKX]); - } - else if(EventAt.code == ABS_Y) - { - NewController->StickAverageY = -1.0f * LinuxNormalizeAxisValue(EventAt.value, GamePadAt->Axes[LSTICKY]); - } - else if(EventAt.code == ABS_HAT0X) - { - NewController->StickAverageX = EventAt.value; - NewController->IsAnalog = false; - } - else if(EventAt.code == ABS_HAT0Y) - { - NewController->StickAverageY = -EventAt.value; - NewController->IsAnalog = false; - } - } break; -#if 0 - if(EventAt.type) printf("%d %d %d\n", EventAt.type, EventAt.code, EventAt.value); -#endif - } - } - } - } - } - - if(!GlobalPaused) - { - if(LinuxState.InputRecordingIndex) - { - LinuxRecordInput(&LinuxState, NewInput); - } - if(LinuxState.InputPlayingIndex) - { - LinuxPlayBackInput(&LinuxState, NewInput); - } - - // NOTE(luca): Clear buffer - MemSet(WindowBuffer, 0, WindowBufferSize); - if(Game.UpdateAndRender) - { - Game.UpdateAndRender(&ThreadContext, &GameMemory, NewInput, &OffscreenBuffer); - } - - /* NOTE(luca): How sound works - -Check the delay -Check the available frames in buffer - -1. Too few audio frames in buffer for current frame. --> Add more - -2. Too many frames available --> Add less / drain? - -3. Fill first time? --> Check delay --> Maybe we should do this every frame? --> Output needed frames to not have lag, this means to output maybe two frames? - -TODO -- check if delay never changes -- check if buffersize never changes --> cache these values -*/ - - - r32 SamplesToWrite = 0; - local_persist b32 AudioFillFirstTime = true; - - r32 SingleFrameOfAudioFrames = TargetSecondsPerFrame*SampleRate; - - if(AudioFillFirstTime) - { - struct timespec WorkCounter = LinuxGetWallClock(); - r32 WorkSecondsElapsed = LinuxGetSecondsElapsed(LastCounter, WorkCounter); - r32 SecondsLeftUntilFlip = TargetSecondsPerFrame - WorkSecondsElapsed; - - if(SecondsLeftUntilFlip > 0) - { - SamplesToWrite = SampleRate*(TargetSecondsPerFrame + SecondsLeftUntilFlip); - } - else - { - SamplesToWrite = SingleFrameOfAudioFrames; - } - - AudioFillFirstTime = false; - } - else - { - SamplesToWrite = SingleFrameOfAudioFrames; - } - - - game_sound_output_buffer SoundBuffer = {}; - SoundBuffer.SamplesPerSecond = SampleRate; - SoundBuffer.SampleCount = SamplesToWrite; - SoundBuffer.Samples = (s16 *)AudioSamples; - - if(Game.GetSoundSamples) - { - Game.GetSoundSamples(&ThreadContext, &GameMemory, &SoundBuffer); - } - -#if 0 - snd_pcm_sframes_t AvailableFrames = 0; - snd_pcm_sframes_t DelayFrames; - - //snd_pcm_avail_update(PCMHandle); - AvailableFrames = snd_pcm_avail(PCMHandle); - snd_pcm_delay(PCMHandle, &DelayFrames); - - //printf("PeriodSize: %lu, PeriodTime: %d, BufferSize: %lu\n", PeriodSize, PeriodTime, PCMBufferSize); - printf("BeingWritten: %lu, Avail: %ld, Delay: %ld, ToWrite: %d\n", PCMBufferSize - AvailableFrames, AvailableFrames, DelayFrames, (s32)SamplesToWrite); -#endif - - LastFramesWritten = snd_pcm_writei(PCMHandle, SoundBuffer.Samples, SoundBuffer.SampleCount); - - if(LastFramesWritten < 0) - { - // TODO(luca): Logging - // NOTE(luca): We might want to silence in case of overruns ahead of time. We also probably want to handle latency differently here. - snd_pcm_recover(PCMHandle, LastFramesWritten, ALSA_RECOVER_SILENT); - - // underrun - if(LastFramesWritten == -EPIPE) - { - AudioFillFirstTime = true; - } - - } - } - -#if 0 - printf("Expected: %d, Delay: %4d, Being written: %5lu, Written: %d, Ptr: %lu\n", - (int)SamplesToWrite, DelayFrames, PCMBufferSize - AvailableFrames, LastFramesWritten, PCMSoundStatus->hw_ptr); -#endif - - struct timespec WorkCounter = LinuxGetWallClock(); - r32 SecondsElapsedForFrame = LinuxGetSecondsElapsed(LastCounter, WorkCounter); - if(SecondsElapsedForFrame < TargetSecondsPerFrame) - { - - s64 SleepUS = (s64)((TargetSecondsPerFrame - 0.001 - SecondsElapsedForFrame)*1000000.0f); - if(SleepUS > 0) - { - usleep(SleepUS); - } - else - { - // TODO(luca): Logging - } - - r32 TestSecondsElapsedForFrame = (r32)(LinuxGetSecondsElapsed(LastCounter, LinuxGetWallClock())); - if(TestSecondsElapsedForFrame < TargetSecondsPerFrame) - { - // TODO(luca): Log missed sleep - } - - // NOTE(luca): This is to help against sleep granularity. - while(SecondsElapsedForFrame < TargetSecondsPerFrame) - { - SecondsElapsedForFrame = LinuxGetSecondsElapsed(LastCounter, LinuxGetWallClock()); - } - } - else - { - // TODO(luca): Log missed frame rate! - } - - struct timespec EndCounter = LinuxGetWallClock(); - r32 MSPerFrame = (r32)(LinuxGetNSecondsElapsed(LastCounter, EndCounter)/1000000.f); - LastCounter = EndCounter; - - XPutImage(DisplayHandle, WindowHandle, DefaultGC, WindowImage, 0, 0, - 0, 0, - Width, Height); - FlipWallClock = LinuxGetWallClock(); - - game_input *TempInput = NewInput; - NewInput = OldInput; - TempInput = NewInput; - -#if 0 - u64 EndCycleCount = __rdtsc(); - u64 CyclesElapsed = EndCycleCount - LastCycleCount; - LastCycleCount = EndCycleCount; - - r64 FPS = 0; - r64 MCPF = (r64)(CyclesElapsed/(1000.0f*1000.0f)); - printf("%.2fms/f %.2ff/s %.2fmc/f\n", MSPerFrame, FPS, MCPF); -#endif - - } - - } - else - { - // TODO: Log this bad WindowHandle - } - } - else - { - // TODO: Log this could not match visual info - } - } - else - { - // TODO: Log could not get x connection - } - return 0; -} |