Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active September 24, 2023 02:08
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mmozeiko/38c64bb65855d783645c to your computer and use it in GitHub Desktop.
Save mmozeiko/38c64bb65855d783645c to your computer and use it in GitHub Desktop.
win32_handmade.cpp with WASAPI from Day 19
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
THIS IS BAD WAY TO USE WASAPI. DO NOT USE MAYBE EXCEPT AS REFERENCE TO WASAPI FUNCTIONS.
/* ========================================================================
$File: $
$Date: $
$Revision: $
$Creator: Casey Muratori $
$Notice: (C) Copyright 2014 by Molly Rocket, Inc. All Rights Reserved. $
======================================================================== */
/*
TODO(casey): THIS IS NOT A FINAL PLATFORM LAYER!!!
- Saved game locations
- Getting a handle to our own executable file
- Asset loading path
- Threading (launch a thread)
- Raw Input (support for multiple keyboards)
- Sleep/timeBeginPeriod
- ClipCursor() (for multimonitor support)
- Fullscreen support
- WM_SETCURSOR (control cursor visibility)
- QueryCancelAutoplay
- WM_ACTIVATEAPP (for when we are not the active application)
- Blit speed improvements (BitBlt)
- Hardware acceleration (OpenGL or Direct3D or BOTH??)
- GetKeyboardLayout (for French keyboards, international WASD support)
Just a partial list of stuff!!
*/
// TODO(casey): Implement sine ourselves
#include <math.h>
#include <stdint.h>
#define internal static
#define local_persist static
#define global_variable static
#define Pi32 3.14159265359f
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
typedef int64_t int64;
typedef int32 bool32;
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef uint64_t uint64;
typedef float real32;
typedef double real64;
#include "handmade.h"
#include "handmade.cpp"
#include <windows.h>
#include <stdio.h>
#include <malloc.h>
#include <xinput.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include "win32_handmade.h"
// TODO(casey): This is a global for now.
global_variable bool32 GlobalRunning;
global_variable win32_offscreen_buffer GlobalBackbuffer;
global_variable IAudioClient* GlobalSoundClient;
global_variable IAudioRenderClient* GlobalSoundRenderClient;
global_variable int64 GlobalPerfCountFrequency;
// NOTE(casey): XInputGetState
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
typedef X_INPUT_GET_STATE(x_input_get_state);
X_INPUT_GET_STATE(XInputGetStateStub)
{
return(ERROR_DEVICE_NOT_CONNECTED);
}
global_variable x_input_get_state *XInputGetState_ = XInputGetStateStub;
#define XInputGetState XInputGetState_
// NOTE(casey): XInputSetState
#define X_INPUT_SET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration)
typedef X_INPUT_SET_STATE(x_input_set_state);
X_INPUT_SET_STATE(XInputSetStateStub)
{
return(ERROR_DEVICE_NOT_CONNECTED);
}
global_variable x_input_set_state *XInputSetState_ = XInputSetStateStub;
#define XInputSetState XInputSetState_
internal debug_read_file_result
DEBUGPlatformReadEntireFile(char *Filename)
{
debug_read_file_result Result = {};
HANDLE FileHandle = CreateFileA(Filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if(FileHandle != INVALID_HANDLE_VALUE)
{
LARGE_INTEGER FileSize;
if(GetFileSizeEx(FileHandle, &FileSize))
{
uint32 FileSize32 = SafeTruncateUInt64(FileSize.QuadPart);
Result.Contents = VirtualAlloc(0, FileSize32, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(Result.Contents)
{
DWORD BytesRead;
if(ReadFile(FileHandle, Result.Contents, FileSize32, &BytesRead, 0) &&
(FileSize32 == BytesRead))
{
// NOTE(casey): File read successfully
Result.ContentsSize = FileSize32;
}
else
{
// TODO(casey): Logging
DEBUGPlatformFreeFileMemory(Result.Contents);
Result.Contents = 0;
}
}
else
{
// TODO(casey): Logging
}
}
else
{
// TODO(casey): Logging
}
CloseHandle(FileHandle);
}
else
{
// TODO(casey): Logging
}
return(Result);
}
internal void
DEBUGPlatformFreeFileMemory(void *Memory)
{
if(Memory)
{
VirtualFree(Memory, 0, MEM_RELEASE);
}
}
internal bool32
DEBUGPlatformWriteEntireFile(char *Filename, uint32 MemorySize, void *Memory)
{
bool32 Result = false;
HANDLE FileHandle = CreateFileA(Filename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
if(FileHandle != INVALID_HANDLE_VALUE)
{
DWORD BytesWritten;
if(WriteFile(FileHandle, Memory, MemorySize, &BytesWritten, 0))
{
// NOTE(casey): File read successfully
Result = (BytesWritten == MemorySize);
}
else
{
// TODO(casey): Logging
}
CloseHandle(FileHandle);
}
else
{
// TODO(casey): Logging
}
return(Result);
}
internal void
Win32LoadXInput(void)
{
// TODO(casey): Test this on Windows 8
HMODULE XInputLibrary = LoadLibraryA("xinput1_4.dll");
if(!XInputLibrary)
{
// TODO(casey): Diagnostic
XInputLibrary = LoadLibraryA("xinput9_1_0.dll");
}
if(!XInputLibrary)
{
// TODO(casey): Diagnostic
XInputLibrary = LoadLibraryA("xinput1_3.dll");
}
if(XInputLibrary)
{
XInputGetState = (x_input_get_state *)GetProcAddress(XInputLibrary, "XInputGetState");
if(!XInputGetState) {XInputGetState = XInputGetStateStub;}
XInputSetState = (x_input_set_state *)GetProcAddress(XInputLibrary, "XInputSetState");
if(!XInputSetState) {XInputSetState = XInputSetStateStub;}
// TODO(casey): Diagnostic
}
else
{
// TODO(casey): Diagnostic
}
}
internal void
Win32InitWASAPI(int32 SamplesPerSecond, int32 BufferSizeInSamples)
{
if (FAILED(CoInitializeEx(0, COINIT_SPEED_OVER_MEMORY)))
{
Assert(!"Error");
}
IMMDeviceEnumerator* Enumerator;
if (FAILED(CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&Enumerator))))
{
Assert(!"Error");
}
IMMDevice* Device;
if (FAILED(Enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &Device)))
{
Assert(!"Error");
}
if (FAILED(Device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (LPVOID*)&GlobalSoundClient)))
{
Assert(!"Error");
}
WAVEFORMATEXTENSIBLE WaveFormat;
WaveFormat.Format.cbSize = sizeof(WaveFormat);
WaveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
WaveFormat.Format.wBitsPerSample = 16;
WaveFormat.Format.nChannels = 2;
WaveFormat.Format.nSamplesPerSec = (DWORD)SamplesPerSecond;
WaveFormat.Format.nBlockAlign = (WORD)(WaveFormat.Format.nChannels * WaveFormat.Format.wBitsPerSample / 8);
WaveFormat.Format.nAvgBytesPerSec = WaveFormat.Format.nSamplesPerSec * WaveFormat.Format.nBlockAlign;
WaveFormat.Samples.wValidBitsPerSample = 16;
WaveFormat.dwChannelMask = KSAUDIO_SPEAKER_STEREO;
WaveFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
REFERENCE_TIME BufferDuration = 10000000ULL * BufferSizeInSamples / SamplesPerSecond; // buffer size in 100 nanoseconds
if (FAILED(GlobalSoundClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_NOPERSIST, BufferDuration, 0, &WaveFormat.Format, nullptr)))
{
Assert(!"Error");
}
if (FAILED(GlobalSoundClient->GetService(IID_PPV_ARGS(&GlobalSoundRenderClient))))
{
Assert(!"Error");
}
UINT32 SoundFrameCount;
if (FAILED(GlobalSoundClient->GetBufferSize(&SoundFrameCount)))
{
Assert(!"Error");
}
// Check if we got what we requested (better would to pass this value back as real buffer size)
Assert(BufferSizeInSamples <= (int32)SoundFrameCount);
}
internal win32_window_dimension
Win32GetWindowDimension(HWND Window)
{
win32_window_dimension Result;
RECT ClientRect;
GetClientRect(Window, &ClientRect);
Result.Width = ClientRect.right - ClientRect.left;
Result.Height = ClientRect.bottom - ClientRect.top;
return(Result);
}
internal void
Win32ResizeDIBSection(win32_offscreen_buffer *Buffer, int Width, int Height)
{
// TODO(casey): Bulletproof this.
// Maybe don't free first, free after, then free first if that fails.
if(Buffer->Memory)
{
VirtualFree(Buffer->Memory, 0, MEM_RELEASE);
}
Buffer->Width = Width;
Buffer->Height = Height;
int BytesPerPixel = 4;
Buffer->BytesPerPixel = BytesPerPixel;
// NOTE(casey): When the biHeight field is negative, this is the clue to
// Windows to treat this bitmap as top-down, not bottom-up, meaning that
// the first three bytes of the image are the color for the top left pixel
// in the bitmap, not the bottom left!
Buffer->Info.bmiHeader.biSize = sizeof(Buffer->Info.bmiHeader);
Buffer->Info.bmiHeader.biWidth = Buffer->Width;
Buffer->Info.bmiHeader.biHeight = -Buffer->Height;
Buffer->Info.bmiHeader.biPlanes = 1;
Buffer->Info.bmiHeader.biBitCount = 32;
Buffer->Info.bmiHeader.biCompression = BI_RGB;
// NOTE(casey): Thank you to Chris Hecker of Spy Party fame
// for clarifying the deal with StretchDIBits and BitBlt!
// No more DC for us.
int BitmapMemorySize = (Buffer->Width*Buffer->Height)*BytesPerPixel;
Buffer->Memory = VirtualAlloc(0, BitmapMemorySize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
Buffer->Pitch = Width*BytesPerPixel;
// TODO(casey): Probably clear this to black
}
internal void
Win32DisplayBufferInWindow(win32_offscreen_buffer *Buffer,
HDC DeviceContext, int WindowWidth, int WindowHeight)
{
// TODO(casey): Aspect ratio correction
// TODO(casey): Play with stretch modes
StretchDIBits(DeviceContext,
/*
X, Y, Width, Height,
X, Y, Width, Height,
*/
0, 0, WindowWidth, WindowHeight,
0, 0, Buffer->Width, Buffer->Height,
Buffer->Memory,
&Buffer->Info,
DIB_RGB_COLORS, SRCCOPY);
}
internal LRESULT CALLBACK
Win32MainWindowCallback(HWND Window,
UINT Message,
WPARAM WParam,
LPARAM LParam)
{
LRESULT Result = 0;
switch(Message)
{
case WM_CLOSE:
{
// TODO(casey): Handle this with a message to the user?
GlobalRunning = false;
} break;
case WM_ACTIVATEAPP:
{
OutputDebugStringA("WM_ACTIVATEAPP\n");
} break;
case WM_DESTROY:
{
// TODO(casey): Handle this as an error - recreate window?
GlobalRunning = false;
} break;
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:
{
Assert(!"Keyboard input came in through a non-dispatch message!");
} break;
case WM_PAINT:
{
PAINTSTRUCT Paint;
HDC DeviceContext = BeginPaint(Window, &Paint);
win32_window_dimension Dimension = Win32GetWindowDimension(Window);
Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext,
Dimension.Width, Dimension.Height);
EndPaint(Window, &Paint);
} break;
default:
{
// OutputDebugStringA("default\n");
Result = DefWindowProcA(Window, Message, WParam, LParam);
} break;
}
return(Result);
}
internal void
Win32FillSoundBuffer(win32_sound_output *SoundOutput, int SamplesToWrite,
game_sound_output_buffer *SourceBuffer)
{
BYTE* SoundBufferData;
if (SUCCEEDED(GlobalSoundRenderClient->GetBuffer((UINT32)SamplesToWrite, &SoundBufferData)))
{
int16* SourceSample = SourceBuffer->Samples;
int16* DestSample = (int16*)SoundBufferData;
for(int SampleIndex = 0;
SampleIndex < SamplesToWrite;
++SampleIndex)
{
*DestSample++ = *SourceSample++;
*DestSample++ = *SourceSample++;
++SoundOutput->RunningSampleIndex;
}
GlobalSoundRenderClient->ReleaseBuffer((UINT32)SamplesToWrite, 0);
}
}
internal void
Win32ProcessKeyboardMessage(game_button_state *NewState, bool32 IsDown)
{
Assert(NewState->EndedDown != IsDown);
NewState->EndedDown = IsDown;
++NewState->HalfTransitionCount;
}
internal void
Win32ProcessXInputDigitalButton(DWORD XInputButtonState,
game_button_state *OldState, DWORD ButtonBit,
game_button_state *NewState)
{
NewState->EndedDown = ((XInputButtonState & ButtonBit) == ButtonBit);
NewState->HalfTransitionCount = (OldState->EndedDown != NewState->EndedDown) ? 1 : 0;
}
internal real32
Win32ProcessXInputStickValue(SHORT Value, SHORT DeadZoneThreshold)
{
real32 Result = 0;
if(Value < -DeadZoneThreshold)
{
Result = (real32)((Value + DeadZoneThreshold) / (32768.0f - DeadZoneThreshold));
}
else if(Value > DeadZoneThreshold)
{
Result = (real32)((Value - DeadZoneThreshold) / (32767.0f - DeadZoneThreshold));
}
return(Result);
}
internal void
Win32ProcessPendingMessages(game_controller_input *KeyboardController)
{
MSG Message;
while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE))
{
switch(Message.message)
{
case WM_QUIT:
{
GlobalRunning = false;
} break;
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:
{
uint32 VKCode = (uint32)Message.wParam;
bool32 WasDown = ((Message.lParam & (1 << 30)) != 0);
bool32 IsDown = ((Message.lParam & (1 << 31)) == 0);
if(WasDown != IsDown)
{
if(VKCode == 'W')
{
Win32ProcessKeyboardMessage(&KeyboardController->MoveUp, IsDown);
}
else if(VKCode == 'A')
{
Win32ProcessKeyboardMessage(&KeyboardController->MoveLeft, IsDown);
}
else if(VKCode == 'S')
{
Win32ProcessKeyboardMessage(&KeyboardController->MoveDown, IsDown);
}
else if(VKCode == 'D')
{
Win32ProcessKeyboardMessage(&KeyboardController->MoveRight, IsDown);
}
else if(VKCode == 'Q')
{
Win32ProcessKeyboardMessage(&KeyboardController->LeftShoulder, IsDown);
}
else if(VKCode == 'E')
{
Win32ProcessKeyboardMessage(&KeyboardController->RightShoulder, IsDown);
}
else if(VKCode == VK_UP)
{
Win32ProcessKeyboardMessage(&KeyboardController->ActionUp, IsDown);
}
else if(VKCode == VK_LEFT)
{
Win32ProcessKeyboardMessage(&KeyboardController->ActionLeft, IsDown);
}
else if(VKCode == VK_DOWN)
{
Win32ProcessKeyboardMessage(&KeyboardController->ActionDown, IsDown);
}
else if(VKCode == VK_RIGHT)
{
Win32ProcessKeyboardMessage(&KeyboardController->ActionRight, IsDown);
}
else if(VKCode == VK_ESCAPE)
{
Win32ProcessKeyboardMessage(&KeyboardController->Start, IsDown);
}
else if(VKCode == VK_SPACE)
{
Win32ProcessKeyboardMessage(&KeyboardController->Back, IsDown);
}
}
bool32 AltKeyWasDown = (Message.lParam & (1 << 29));
if((VKCode == VK_F4) && AltKeyWasDown)
{
GlobalRunning = false;
}
} break;
default:
{
TranslateMessage(&Message);
DispatchMessageA(&Message);
} break;
}
}
}
inline LARGE_INTEGER
Win32GetWallClock(void)
{
LARGE_INTEGER Result;
QueryPerformanceCounter(&Result);
return(Result);
}
inline real32
Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End)
{
real32 Result = ((real32)(End.QuadPart - Start.QuadPart) /
(real32)GlobalPerfCountFrequency);
return(Result);
}
internal void
Win32DebugDrawVertical(win32_offscreen_buffer *GlobalBackbuffer,
int X, int Top, int Bottom, uint32 Color)
{
uint8 *Pixel = ((uint8 *)GlobalBackbuffer->Memory +
X*GlobalBackbuffer->BytesPerPixel +
Top*GlobalBackbuffer->Pitch);
for(int Y = Top;
Y < Bottom;
++Y)
{
*(uint32 *)Pixel = Color;
Pixel += GlobalBackbuffer->Pitch;
}
}
inline void
Win32DrawSoundBufferMarker(win32_offscreen_buffer *Backbuffer,
win32_sound_output *SoundOutput,
real32 C, int PadX, int Top, int Bottom,
DWORD Value, uint32 Color)
{
Assert(Value < SoundOutput->SecondaryBufferSize);
real32 XReal32 = (C * (real32)Value);
int X = PadX + (int)XReal32;
Win32DebugDrawVertical(Backbuffer, X, Top, Bottom, Color);
}
internal void
Win32DebugSyncDisplay(win32_offscreen_buffer *Backbuffer,
int MarkerCount, win32_debug_time_marker *Markers,
win32_sound_output *SoundOutput, real32 TargetSecondsPerFrame)
{
// TODO(casey): Draw where we're writing out sound
int PadX = 16;
int PadY = 16;
int Top = PadY;
int Bottom = Backbuffer->Height - PadY;
real32 C = (real32)(Backbuffer->Width - 2*PadX) / (real32)SoundOutput->SecondaryBufferSize;
for(int MarkerIndex = 0;
MarkerIndex < MarkerCount;
++MarkerIndex)
{
win32_debug_time_marker *ThisMarker = &Markers[MarkerIndex];
Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->PlayCursor, 0xFFFFFFFF);
// Win32DrawSoundBufferMarker(Backbuffer, SoundOutput, C, PadX, Top, Bottom, ThisMarker->WriteCursor, 0xFFFF0000);
}
}
int CALLBACK
WinMain(HINSTANCE Instance,
HINSTANCE PrevInstance,
LPSTR CommandLine,
int ShowCode)
{
LARGE_INTEGER PerfCountFrequencyResult;
QueryPerformanceFrequency(&PerfCountFrequencyResult);
GlobalPerfCountFrequency = PerfCountFrequencyResult.QuadPart;
// NOTE(casey): Set the Windows scheduler granularity to 1ms
// so that our Sleep() can be more granular.
UINT DesiredSchedulerMS = 1;
bool32 SleepIsGranular = (timeBeginPeriod(DesiredSchedulerMS) == TIMERR_NOERROR);
Win32LoadXInput();
WNDCLASSA WindowClass = {};
Win32ResizeDIBSection(&GlobalBackbuffer, 1280, 720);
WindowClass.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
WindowClass.lpfnWndProc = Win32MainWindowCallback;
WindowClass.hInstance = Instance;
// WindowClass.hIcon;
WindowClass.lpszClassName = "HandmadeHeroWindowClass";
// TODO(casey): How do we reliably query on this on Windows?
// TODO(casey): Let's think about running non-frame-quantized for audio latency...
// TODO(casey): Let's use the write cursor delta from the play cursor to adjust
// the target audio latency.
#define FramesOfAudioLatency 1
#define MonitorRefreshHz 60
#define GameUpdateHz (MonitorRefreshHz / 2)
real32 TargetSecondsPerFrame = 1.0f / (real32)GameUpdateHz;
if(RegisterClassA(&WindowClass))
{
HWND Window =
CreateWindowExA(
0,
WindowClass.lpszClassName,
"Handmade Hero",
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
Instance,
0);
if(Window)
{
// NOTE(casey): Since we specified CS_OWNDC, we can just
// get one device context and use it forever because we
// are not sharing it with anyone.
HDC DeviceContext = GetDC(Window);
win32_sound_output SoundOutput = {};
// TODO(casey): Make this like sixty seconds?
SoundOutput.SamplesPerSecond = 48000;
SoundOutput.BytesPerSample = sizeof(int16)*2;
SoundOutput.SecondaryBufferSize = SoundOutput.SamplesPerSecond;
SoundOutput.LatencySampleCount = FramesOfAudioLatency*(SoundOutput.SamplesPerSecond / GameUpdateHz);
Win32InitWASAPI(SoundOutput.SamplesPerSecond, SoundOutput.SecondaryBufferSize);
GlobalSoundClient->Start();
GlobalRunning = true;
// TODO(casey): Pool with bitmap VirtualAlloc
int16 *Samples = (int16 *)VirtualAlloc(0, SoundOutput.SecondaryBufferSize,
MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
#if HANDMADE_INTERNAL
LPVOID BaseAddress = (LPVOID)Terabytes(2);
#else
LPVOID BaseAddress = 0;
#endif
game_memory GameMemory = {};
GameMemory.PermanentStorageSize = Megabytes(64);
GameMemory.TransientStorageSize = Gigabytes(1);
// TODO(casey): Handle various memory footprints (USING SYSTEM METRICS)
uint64 TotalSize = GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize;
GameMemory.PermanentStorage = VirtualAlloc(BaseAddress, (size_t)TotalSize,
MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
GameMemory.TransientStorage = ((uint8 *)GameMemory.PermanentStorage +
GameMemory.PermanentStorageSize);
if(Samples && GameMemory.PermanentStorage && GameMemory.TransientStorage)
{
game_input Input[2] = {};
game_input *NewInput = &Input[0];
game_input *OldInput = &Input[1];
LARGE_INTEGER LastCounter = Win32GetWallClock();
int DebugTimeMarkerIndex = 0;
win32_debug_time_marker DebugTimeMarkers[GameUpdateHz] = {0};
DWORD LastPlayCursor = 0;
bool32 SoundIsValid = false;
uint64 LastCycleCount = __rdtsc();
while(GlobalRunning)
{
// TODO(casey): Zeroing macro
// TODO(casey): We can't zero everything because the up/down state will
// be wrong!!!
game_controller_input *OldKeyboardController = GetController(OldInput, 0);
game_controller_input *NewKeyboardController = GetController(NewInput, 0);
*NewKeyboardController = {};
NewKeyboardController->IsConnected = true;
for(int ButtonIndex = 0;
ButtonIndex < ArrayCount(NewKeyboardController->Buttons);
++ButtonIndex)
{
NewKeyboardController->Buttons[ButtonIndex].EndedDown =
OldKeyboardController->Buttons[ButtonIndex].EndedDown;
}
Win32ProcessPendingMessages(NewKeyboardController);
// TODO(casey): Need to not poll disconnected controllers to avoid
// xinput frame rate hit on older libraries...
// TODO(casey): Should we poll this more frequently
DWORD MaxControllerCount = XUSER_MAX_COUNT;
if(MaxControllerCount > (ArrayCount(NewInput->Controllers) - 1))
{
MaxControllerCount = (ArrayCount(NewInput->Controllers) - 1);
}
for (DWORD ControllerIndex = 0;
ControllerIndex < MaxControllerCount;
++ControllerIndex)
{
DWORD OurControllerIndex = ControllerIndex + 1;
game_controller_input *OldController = GetController(OldInput, OurControllerIndex);
game_controller_input *NewController = GetController(NewInput, OurControllerIndex);
XINPUT_STATE ControllerState;
if(XInputGetState(ControllerIndex, &ControllerState) == ERROR_SUCCESS)
{
NewController->IsConnected = true;
// NOTE(casey): This controller is plugged in
// TODO(casey): See if ControllerState.dwPacketNumber increments too rapidly
XINPUT_GAMEPAD *Pad = &ControllerState.Gamepad;
// TODO(casey): This is a square deadzone, check XInput to
// verify that the deadzone is "round" and show how to do
// round deadzone processing.
NewController->StickAverageX = Win32ProcessXInputStickValue(
Pad->sThumbLX, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
NewController->StickAverageY = Win32ProcessXInputStickValue(
Pad->sThumbLY, XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
if((NewController->StickAverageX != 0.0f) ||
(NewController->StickAverageY != 0.0f))
{
NewController->IsAnalog = true;
}
if(Pad->wButtons & XINPUT_GAMEPAD_DPAD_UP)
{
NewController->StickAverageY = 1.0f;
NewController->IsAnalog = false;
}
if(Pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN)
{
NewController->StickAverageY = -1.0f;
NewController->IsAnalog = false;
}
if(Pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT)
{
NewController->StickAverageX = -1.0f;
NewController->IsAnalog = false;
}
if(Pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
{
NewController->StickAverageX = 1.0f;
NewController->IsAnalog = false;
}
real32 Threshold = 0.5f;
Win32ProcessXInputDigitalButton(
(NewController->StickAverageX < -Threshold) ? 1 : 0,
&OldController->MoveLeft, 1,
&NewController->MoveLeft);
Win32ProcessXInputDigitalButton(
(NewController->StickAverageX > Threshold) ? 1 : 0,
&OldController->MoveRight, 1,
&NewController->MoveRight);
Win32ProcessXInputDigitalButton(
(NewController->StickAverageY < -Threshold) ? 1 : 0,
&OldController->MoveDown, 1,
&NewController->MoveDown);
Win32ProcessXInputDigitalButton(
(NewController->StickAverageY > Threshold) ? 1 : 0,
&OldController->MoveUp, 1,
&NewController->MoveUp);
Win32ProcessXInputDigitalButton(Pad->wButtons,
&OldController->ActionDown, XINPUT_GAMEPAD_A,
&NewController->ActionDown);
Win32ProcessXInputDigitalButton(Pad->wButtons,
&OldController->ActionRight, XINPUT_GAMEPAD_B,
&NewController->ActionRight);
Win32ProcessXInputDigitalButton(Pad->wButtons,
&OldController->ActionLeft, XINPUT_GAMEPAD_X,
&NewController->ActionLeft);
Win32ProcessXInputDigitalButton(Pad->wButtons,
&OldController->ActionUp, XINPUT_GAMEPAD_Y,
&NewController->ActionUp);
Win32ProcessXInputDigitalButton(Pad->wButtons,
&OldController->LeftShoulder, XINPUT_GAMEPAD_LEFT_SHOULDER,
&NewController->LeftShoulder);
Win32ProcessXInputDigitalButton(Pad->wButtons,
&OldController->RightShoulder, XINPUT_GAMEPAD_RIGHT_SHOULDER,
&NewController->RightShoulder);
Win32ProcessXInputDigitalButton(Pad->wButtons,
&OldController->Start, XINPUT_GAMEPAD_START,
&NewController->Start);
Win32ProcessXInputDigitalButton(Pad->wButtons,
&OldController->Back, XINPUT_GAMEPAD_BACK,
&NewController->Back);
}
else
{
// NOTE(casey): The controller is not available
NewController->IsConnected = false;
}
}
// NOTE(casey): Compute how much sound to write and where
int SamplesToWrite = 0;
UINT32 SoundPaddingSize;
if (SUCCEEDED(GlobalSoundClient->GetCurrentPadding(&SoundPaddingSize)))
{
SamplesToWrite = (int)(SoundOutput.SecondaryBufferSize - SoundPaddingSize);
if (SamplesToWrite > SoundOutput.LatencySampleCount)
{
SamplesToWrite = SoundOutput.LatencySampleCount;
}
}
game_sound_output_buffer SoundBuffer = {};
SoundBuffer.SamplesPerSecond = SoundOutput.SamplesPerSecond;
SoundBuffer.SampleCount = SamplesToWrite;
SoundBuffer.Samples = Samples;
game_offscreen_buffer Buffer = {};
Buffer.Memory = GlobalBackbuffer.Memory;
Buffer.Width = GlobalBackbuffer.Width;
Buffer.Height = GlobalBackbuffer.Height;
Buffer.Pitch = GlobalBackbuffer.Pitch;
GameUpdateAndRender(&GameMemory, NewInput, &Buffer, &SoundBuffer);
Win32FillSoundBuffer(&SoundOutput, SamplesToWrite, &SoundBuffer);
LARGE_INTEGER WorkCounter = Win32GetWallClock();
real32 WorkSecondsElapsed = Win32GetSecondsElapsed(LastCounter, WorkCounter);
// TODO(casey): NOT TESTED YET! PROBABLY BUGGY!!!!!
real32 SecondsElapsedForFrame = WorkSecondsElapsed;
if(SecondsElapsedForFrame < TargetSecondsPerFrame)
{
if(SleepIsGranular)
{
DWORD SleepMS = (DWORD)(1000.0f * (TargetSecondsPerFrame -
SecondsElapsedForFrame));
if(SleepMS > 0)
{
Sleep(SleepMS);
}
}
real32 TestSecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter,
Win32GetWallClock());
// Assert(TestSecondsElapsedForFrame < TargetSecondsPerFrame);
while(SecondsElapsedForFrame < TargetSecondsPerFrame)
{
SecondsElapsedForFrame = Win32GetSecondsElapsed(LastCounter,
Win32GetWallClock());
}
}
else
{
// TODO(casey): MISSED FRAME RATE!
// TODO(casey): Logging
}
LARGE_INTEGER EndCounter = Win32GetWallClock();
real32 MSPerFrame = 1000.0f*Win32GetSecondsElapsed(LastCounter, EndCounter);
LastCounter = EndCounter;
win32_window_dimension Dimension = Win32GetWindowDimension(Window);
Win32DebugSyncDisplay(&GlobalBackbuffer, ArrayCount(DebugTimeMarkers), DebugTimeMarkers,
&SoundOutput, TargetSecondsPerFrame);
Win32DisplayBufferInWindow(&GlobalBackbuffer, DeviceContext,
Dimension.Width, Dimension.Height);
#if HANDMADE_INTERNAL
// NOTE(casey): This is debug code
{
win32_debug_time_marker *Marker = &DebugTimeMarkers[DebugTimeMarkerIndex++];
if(DebugTimeMarkerIndex >= ArrayCount(DebugTimeMarkers))
{
DebugTimeMarkerIndex = 0;
}
UINT64 PositionFrequency;
UINT64 PositionUnits;
IAudioClock* AudioClock;
GlobalSoundClient->GetService(IID_PPV_ARGS(&AudioClock));
AudioClock->GetFrequency(&PositionFrequency);
AudioClock->GetPosition(&PositionUnits, 0);
AudioClock->Release();
Marker->PlayCursor = (DWORD)(SoundOutput.SamplesPerSecond * PositionUnits / PositionFrequency) % SoundOutput.SamplesPerSecond;
}
#endif
game_input *Temp = NewInput;
NewInput = OldInput;
OldInput = Temp;
// TODO(casey): Should I clear these here?
uint64 EndCycleCount = __rdtsc();
uint64 CyclesElapsed = EndCycleCount - LastCycleCount;
LastCycleCount = EndCycleCount;
real64 FPS = 0.0f;
real64 MCPF = ((real64)CyclesElapsed / (1000.0f * 1000.0f));
char FPSBuffer[256];
_snprintf_s(FPSBuffer, sizeof(FPSBuffer),
"%.02fms/f, %.02ff/s, %.02fmc/f\n", MSPerFrame, FPS, MCPF);
OutputDebugStringA(FPSBuffer);
}
}
else
{
// TODO(casey): Logging
}
}
else
{
// TODO(casey): Logging
}
}
else
{
// TODO(casey): Logging
}
return(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment