aboutsummaryrefslogtreecommitdiff
path: root/linux_handmade.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'linux_handmade.cpp')
-rw-r--r--linux_handmade.cpp985
1 files changed, 985 insertions, 0 deletions
diff --git a/linux_handmade.cpp b/linux_handmade.cpp
new file mode 100644
index 0000000..1074685
--- /dev/null
+++ b/linux_handmade.cpp
@@ -0,0 +1,985 @@
+#include <stdio.h>
+#include <time.h>
+#include <x86intrin.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.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.h"
+#include "linux_handmade.h"
+
+#define true 1
+#define false 0
+
+#define MAX_PLAYER_COUNT 4
+
+// 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++;
+}
+
+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++;
+ }
+}
+
+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->FileFD, 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->FileFD = open(FilePath, O_RDWR|O_NONBLOCK);
+
+ if(GamePad->FileFD != -1)
+ {
+ int Version = 0;
+ int IsCompatible = true;
+
+ // TODO(luca): Check versions
+ ioctl(GamePad->FileFD, EVIOCGVERSION, &Version);
+ ioctl(GamePad->FileFD, EVIOCGNAME(sizeof(GamePad->Name)), GamePad->Name);
+
+ char SupportedEventBits[BytesNeededForBits(EV_MAX)] = {};
+ if(ioctl(GamePad->FileFD, 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->FileFD, 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->FileFD);
+ *GamePad = {};
+ GamePad->FileFD = -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", 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 LinuxGetLibraryPath(char *Dest, char *ExecutablePath, char *LibraryFileName)
+{
+ size_t LastSlash = 0;
+ for(char *Scan = ExecutablePath;
+ *Scan;
+ Scan++)
+ {
+ if(*Scan == '/')
+ {
+ LastSlash = Scan - ExecutablePath;
+ }
+ }
+
+ for(size_t Index = 0;
+ Index < LastSlash + 1;
+ Index++)
+ {
+ *Dest++ = *ExecutablePath++;
+ }
+
+ while(*LibraryFileName) *Dest++ = *LibraryFileName++;
+}
+
+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 LinuxProcessPendingMessages(Display *DisplayHandle, Window WindowHandle,
+ Atom WM_DELETE_WINDOW, game_controller_input *KeyboardController)
+{
+ XEvent WindowEvent = {};
+ while(XPending(DisplayHandle) > 0)
+ {
+ XNextEvent(DisplayHandle, &WindowEvent);
+ switch (WindowEvent.type)
+ {
+ case KeyPress:
+ case KeyRelease:
+ {
+ KeySym keysym = XLookupKeysym(&WindowEvent.xkey, 0);
+
+ b32 IsDown = (WindowEvent.type == KeyPress);
+
+ if(0) {}
+ else if(keysym == XK_w)
+ {
+ LinuxProcessKeyPress(&KeyboardController->MoveUp, IsDown);
+ }
+ else if(keysym == XK_a)
+ {
+ LinuxProcessKeyPress(&KeyboardController->MoveLeft, IsDown);
+ }
+ else if(keysym == XK_r)
+ {
+ LinuxProcessKeyPress(&KeyboardController->MoveDown, IsDown);
+ }
+ else if(keysym == XK_s)
+ {
+ LinuxProcessKeyPress(&KeyboardController->MoveRight, IsDown);
+ }
+ else if(keysym == XK_Up)
+ {
+ LinuxProcessKeyPress(&KeyboardController->ActionUp, IsDown);
+ }
+ else if(keysym == XK_Left)
+ {
+ LinuxProcessKeyPress(&KeyboardController->ActionLeft, IsDown);
+ }
+ else if(keysym == XK_Down)
+ {
+ LinuxProcessKeyPress(&KeyboardController->ActionDown, IsDown);
+ }
+ else if(keysym == XK_Right)
+ {
+ LinuxProcessKeyPress(&KeyboardController->ActionRight, IsDown);
+ }
+ else if(keysym == XK_p)
+ {
+ if(IsDown)
+ {
+ GlobalPaused = !GlobalPaused;
+ }
+ }
+ else if(keysym == XK_Escape ||
+ keysym == XK_q)
+ {
+ 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;
+ }
+ }
+
+}
+
+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 FD = open(FileName, O_RDONLY);
+ if(FD != -1)
+ {
+ struct stat FileStats = {};
+ fstat(FD, &FileStats);
+ Result.ContentsSize = FileStats.st_size;
+ Result.Contents = mmap(0, FileStats.st_size, PROT_READ, MAP_PRIVATE, FD, 0);
+
+ close(FD);
+ }
+
+ 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 LinuxGetSecondsElapsed(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 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[])
+{
+ Display *DisplayHandle = XOpenDisplay(0);
+
+ if(DisplayHandle)
+ {
+ Window RootWindow = XDefaultRootWindow(DisplayHandle);
+ int Screen = XDefaultScreen(DisplayHandle);
+ int Width = 800;
+ int Height = 600;
+ int ScreenBitDepth = 24;
+ XVisualInfo WindowVisualInfo = {};
+ if(XMatchVisualInfo(DisplayHandle, Screen, ScreenBitDepth, TrueColor, &WindowVisualInfo))
+ {
+ XSetWindowAttributes WindowAttributes = {};
+ WindowAttributes.bit_gravity = StaticGravity;
+ WindowAttributes.background_pixel = 0;
+ WindowAttributes.colormap = XCreateColormap(DisplayHandle, RootWindow, WindowVisualInfo.visual, AllocNone);
+ WindowAttributes.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask;
+ u64 WindowAttributeMask = CWBitGravity | CWBackPixel | CWColormap | CWEventMask;
+
+ Window WindowHandle = XCreateWindow(DisplayHandle, RootWindow,
+ 1920 - Width - 2, 1080 - Height - 2,
+ 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))
+ {
+ printf("Couldn't register WM_DELETE_WINDOW property\n");
+ }
+
+ XClassHint ClassHint = {};
+ ClassHint.res_name = "Handmade Window";
+ ClassHint.res_class = "Handmade Window";
+ XSetClassHint(DisplayHandle, WindowHandle, &ClassHint);
+
+ int BitsPerPixel = 32;
+ int BytesPerPixel = BitsPerPixel/8;
+ int WindowBufferSize = Width*Height*BytesPerPixel;
+ char *WindowBufferMemory = (char *)malloc(WindowBufferSize);
+
+ XImage *WindowBuffer = XCreateImage(DisplayHandle, WindowVisualInfo.visual, WindowVisualInfo.depth, ZPixmap, 0, WindowBufferMemory, Width, Height, BitsPerPixel, 0);
+ GC DefaultGC = DefaultGC(DisplayHandle, Screen);
+
+ char LibraryFullPath[PATH_MAX] = {};
+ char LibraryName[] = "handmade.so";
+ LinuxGetLibraryPath(LibraryFullPath, Args[0], LibraryName);
+ 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;
+
+#if HANDMADE_INTERNAL
+ void *BaseAddress = (void *)Terabytes(2);
+#else
+ void *BaseAddress = 0;
+#endif
+
+ u64 TotalSize = GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize;
+ GameMemory.PermanentStorage = (char *)mmap(BaseAddress, TotalSize, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_SHARED, -1, 0);
+ 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].FileFD = -1;
+ }
+
+ // TODO(luca): Run this once on startup to detect connected controllers and later with inotify when controllers are added.
+ 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->FileFD != -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 = WindowBufferMemory;
+ OffscreenBuffer.Width = Width;
+ OffscreenBuffer.Height = Height;
+ OffscreenBuffer.BytesPerPixel = BytesPerPixel;
+ OffscreenBuffer.Pitch = Width*BytesPerPixel;
+
+ int LastFramesWritten = 0;
+ unsigned int SampleRate, ChannelCount, TimePeriod, 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, &TimePeriod, NULL);
+ snd_pcm_hw_params_get_buffer_size(PCMParams, &PCMBufferSize);
+ snd_pcm_status_malloc(&PCMStatus);
+
+ char AudioSamples[PCMBufferSize];
+ u64 Periods = 2;
+ u32 BytesPerSample = (sizeof(s16)*ChannelCount);
+
+#if 1
+ r32 GameUpdateHz = 30;
+#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)
+ {
+
+#if HANDMADE_INTERNAL
+ // 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 = LinuxGetSecondsElapsed(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;
+
+ LinuxProcessPendingMessages(DisplayHandle, WindowHandle, WM_DELETE_WINDOW, 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;
+ NewInput->MouseButtons[0].EndedDown = ((MouseMask & Button1Mask) > 0);
+ NewInput->MouseButtons[1].EndedDown = ((MouseMask & Button2Mask) > 0);
+ NewInput->MouseButtons[2].EndedDown = ((MouseMask & Button3Mask) > 0);
+ }
+ }
+
+ for(int GamePadIndex = 0;
+ GamePadIndex < MAX_PLAYER_COUNT;
+ GamePadIndex++)
+ {
+ linux_gamepad *GamePadAt = &GamePads[GamePadIndex];
+
+ if(GamePadAt->FileFD != -1)
+ {
+ game_controller_input *OldController = GetController(OldInput, 0);
+ game_controller_input *NewController = GetController(NewInput, 0);
+
+ // TODO(luca): What we really want is the last event. (or haltransitions)
+ struct input_event InputEvents[64] = {};
+ int BytesRead = 0;
+
+ BytesRead = read(GamePadAt->FileFD, InputEvents, sizeof(InputEvents));
+ if(BytesRead != -1)
+ {
+ for(int EventIndex = 0;
+ EventIndex < (int)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
+ }
+
+ }
+ }
+ }
+
+
+ // NOTE(luca): Buffer is cleared just in case.
+#if 1
+ for(int X = 0; X < OffscreenBuffer.Width; X++)
+ {
+ for(int Y = 0; Y < OffscreenBuffer.Height; Y++)
+ {
+ ((u32 *)OffscreenBuffer.Memory)[X + Y*OffscreenBuffer.Width] = 0;
+ }
+ }
+#endif
+
+ if(!GlobalPaused)
+ {
+ if(Game.UpdateAndRender)
+ {
+ Game.UpdateAndRender(&ThreadContext, &GameMemory, NewInput, &OffscreenBuffer);
+ }
+ }
+
+ r32 SamplesToWrite = TargetSecondsPerFrame*SampleRate;
+ local_persist b32 AudioFillFirstTime = true;
+ r32 SecondsSinceFlip = (LinuxGetSecondsElapsed(FlipWallClock, LinuxGetWallClock()))/1000000000.0f;
+ r32 SecondsLeftUntilFlip = TargetSecondsPerFrame - SecondsSinceFlip;
+ if(AudioFillFirstTime)
+ {
+ SamplesToWrite += SecondsLeftUntilFlip*SampleRate;
+ AudioFillFirstTime = false;
+ }
+
+#if HANDMADE_INTERNAL
+ snd_htimestamp_t AudioTimeStamp = {};
+ snd_pcm_status(PCMHandle, PCMStatus);
+ sound_status *PCMSoundStatus = (sound_status *)PCMStatus;
+ u32 AvailableFrames = snd_pcm_status_get_avail(PCMStatus);
+ s32 DelayFrames = snd_pcm_status_get_delay(PCMStatus);
+ snd_pcm_status_get_htstamp(PCMStatus, &AudioTimeStamp);
+ struct timespec AudioWallClock = LinuxGetWallClock();
+
+#if 0
+ // TODO(luca): Why does this work?
+ if(DelayFrames > SamplesToWrite && DelayFrames > 0)
+ {
+ SamplesToWrite -= (DelayFrames - SamplesToWrite);
+ if(SamplesToWrite < 0)
+ {
+ SamplesToWrite = 0;
+ }
+ }
+#endif
+
+ u32 PadY = 16;
+ u32 LineHeight = 38;
+ u32 X = 0;
+ u32 Y = 0;
+
+ u32 MapIntoWidth = (OffscreenBuffer.Width);
+
+ X = MapIntoWidth*(r32)((r32)SamplesToWrite/(r32)SampleRate);
+ LinuxDebugVerticalLine(&OffscreenBuffer, X, Y*LineHeight + PadY, 0x0030FF00);
+
+ X = MapIntoWidth*(r32)((r32)AvailableFrames/(r32)PCMBufferSize);
+ LinuxDebugVerticalLine(&OffscreenBuffer, X, Y*LineHeight + PadY, 0x0030FF00);
+
+ X = MapIntoWidth*(r32)((r32)DelayFrames/(r32)PCMBufferSize);
+ LinuxDebugVerticalLine(&OffscreenBuffer, X, Y*LineHeight + PadY, 0x00FF00FF);
+
+ Y++;
+
+ X = MapIntoWidth*(r32)((r32)SecondsSinceFlip/TargetSecondsPerFrame);
+ LinuxDebugVerticalLine(&OffscreenBuffer, X, Y*LineHeight + PadY, 0x0000FF00);
+
+#endif
+
+ game_sound_output_buffer SoundBuffer = {};
+ SoundBuffer.SamplesPerSecond = SampleRate;
+ SoundBuffer.SampleCount = SamplesToWrite;
+ SoundBuffer.Samples = (s16 *)AudioSamples;
+
+ if(Game.GetSoundSamples)
+ {
+ Game.GetSoundSamples(&ThreadContext, &GameMemory, &SoundBuffer);
+ }
+
+ 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, 0);
+ }
+
+ printf("Expected: %d, Delay: %4d, Being written: %5lu, Written: %d, Ptr: %lu\n",
+ (int)SamplesToWrite, DelayFrames, PCMBufferSize - AvailableFrames, LastFramesWritten, PCMSoundStatus->hw_ptr);
+
+ struct timespec WorkCounter = LinuxGetWallClock();
+ r32 WorkSecondsElapsed = LinuxGetSecondsElapsed(LastCounter, WorkCounter);
+ r32 SecondsElapsedForFrame = (WorkSecondsElapsed/1000000000.0f);
+ 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
+ }
+
+ while(SecondsElapsedForFrame < TargetSecondsPerFrame)
+ {
+ SecondsElapsedForFrame = LinuxGetSecondsElapsed(LastCounter, LinuxGetWallClock())/1000000000.0f;
+ }
+ }
+ else
+ {
+ // TODO(luca): Log missed frame rate!
+ }
+
+ struct timespec EndCounter = LinuxGetWallClock();
+ r32 MSPerFrame = (r32)(LinuxGetSecondsElapsed(LastCounter, EndCounter)/1000000.f);
+ LastCounter = EndCounter;
+
+ XPutImage(DisplayHandle, WindowHandle, DefaultGC, WindowBuffer, 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;
+}