Created
August 13, 2017 13:34
-
-
Save mrennix/60384f0e86a655ba8180fa8a6c7cb3ca to your computer and use it in GitHub Desktop.
Handmade Hero Directsound clicking
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define UNICODE | |
#define _UNICODE | |
#include <windows.h> | |
#include <tchar.h> | |
#include <stdint.h> | |
#include <dsound.h> | |
#include <xinput.h> | |
#include <cstdio> | |
#include <cmath> | |
// Casey's defines | |
#define local_persist static | |
#define global_variable static | |
#define internal static | |
// Typedefs | |
typedef uint8_t uint8; | |
typedef uint16_t uint16; | |
typedef uint32_t uint32; | |
typedef uint64_t uint64; | |
typedef int8_t int8; | |
typedef int16_t int16; | |
typedef int32_t int32; | |
typedef int64_t int64; | |
typedef float real32; | |
typedef double real64; | |
typedef int32_t bool32; // so that 0 = true, non-zero = false | |
struct win32_offscreen_buffer | |
{ | |
BITMAPINFO Info; | |
void *Memory; | |
int Width; | |
int Height; | |
int Pitch; | |
int BytesPerPixel; | |
}; | |
struct Win32_Window_Dimension | |
{ | |
int Width; | |
int Height; | |
}; | |
#define PI32 3.14159265359f | |
// Stub for XInputGetState to use if XInput DLL not found | |
#define X_INPUT_GET_STATE(name) DWORD WINAPI name (DWORD dwUserIndex, XINPUT_STATE* pState) | |
typedef X_INPUT_GET_STATE(x_input_get_state_t); | |
X_INPUT_GET_STATE(XInputGetStateStub) | |
{ | |
UNREFERENCED_PARAMETER(dwUserIndex); | |
UNREFERENCED_PARAMETER(pState); | |
return ERROR_DEVICE_NOT_CONNECTED; | |
} | |
global_variable x_input_get_state_t *XInputGetState_ = XInputGetStateStub; | |
#define XInputGetState XInputGetState_ // remap the Xinput API function to our stub function | |
// Stub for XInputSetState to use if XInput DLL not found | |
#define X_INPUT_SET_STATE(name) DWORD WINAPI name (DWORD dwUserIndex, XINPUT_VIBRATION* pVibration) | |
typedef X_INPUT_SET_STATE(x_input_set_state_t); | |
X_INPUT_SET_STATE(XInputSetStateStub) | |
{ | |
UNREFERENCED_PARAMETER(dwUserIndex); | |
UNREFERENCED_PARAMETER(pVibration); | |
return ERROR_DEVICE_NOT_CONNECTED; | |
} | |
global_variable x_input_set_state_t *XInputSetState_ = XInputSetStateStub; | |
#define XInputSetState XInputSetState_ // remap the Xinput API function to our stub function | |
// Stub for DirectSoundCreate to use if dsound DLL not found | |
#define DIRECT_SOUND_CREATE(name) HRESULT WINAPI name(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter); | |
typedef DIRECT_SOUND_CREATE(direct_sound_create_t); | |
// Global variables | |
#define TITLE L"Handemade Hero" | |
global_variable const wchar_t szTitle[] = TITLE; | |
global_variable const wchar_t log_file[] = TITLE".log"; | |
FILE *f_log; | |
#define LOG(string) fwprintf_s(f_log, _T(string));fflush(f_log) | |
global_variable bool GlobalRunning; // Game running? | |
global_variable win32_offscreen_buffer GlobalBackBuffer; | |
global_variable int xOffset = 0; | |
global_variable int yOffset = 0; | |
bool sound_is_playing = false; | |
global_variable LPDIRECTSOUNDBUFFER GlobalSecondaryBuffer; | |
internal void | |
win32_init_logging() | |
{ | |
if (_wfopen_s(&f_log, log_file, L"w") != 0) | |
{ | |
wprintf(L"Error opening log file %Ls\n", log_file); | |
exit(1); | |
} | |
} | |
/* | |
Initialise XInput for XBOX gamepad controllers | |
Windows 8 and above - xinput1_4.dll | |
Windows 7 - xinput1_3.dll | |
Windows Vista - xinput9_1_0.dll; | |
*/ | |
internal void | |
win32_load_XInput() | |
{ | |
HMODULE XInputLibrary = LoadLibraryW(L"xinput1_4.dll"); | |
if (!XInputLibrary) | |
{ | |
XInputLibrary = LoadLibraryW(L"xinput1_3.dll"); | |
if (!XInputLibrary) | |
{ | |
XInputLibrary = LoadLibraryW(L"xinput9_1_0.dll"); | |
if (!XInputLibrary) | |
{ | |
// TODO: no XInput DLL found - print error | |
LOG("xinput DLL load FAILED - unable to use gamepad.\n"); | |
} | |
else | |
{ | |
LOG("xinput9_1_0.dll load SUCCEEDED.\n"); | |
} | |
} | |
else | |
{ | |
LOG("xinput1_3.dll load SUCCEEDED.\n"); | |
} | |
} | |
else | |
{ | |
LOG("xinput1_4.dll load SUCCEEDED.\n"); | |
} | |
if (XInputLibrary) | |
{ | |
XInputGetState = (x_input_get_state_t *)GetProcAddress(XInputLibrary, "XInputGetState"); | |
XInputSetState = (x_input_set_state_t *)GetProcAddress(XInputLibrary, "XInputSetState"); | |
} | |
} | |
internal void | |
win32_init_dsound(HWND hwnd, int32 samples_per_sec, int32 buffer_size) | |
{ | |
HMODULE dsound_library = LoadLibrary(_T("dsound.dll")); | |
LPDIRECTSOUND ppDS; | |
if (dsound_library) | |
{ | |
LOG("dsound.dll load SUCCEEDED.\n"); | |
direct_sound_create_t *DirectSoundCreate = (direct_sound_create_t *)GetProcAddress(dsound_library, "DirectSoundCreate"); | |
if (DirectSoundCreate) | |
{ | |
LOG("GetProcAddress(DirectSoundCreate) SUCCEEDED.\n"); | |
if (DirectSoundCreate(0, &ppDS, 0) == DS_OK) | |
{ | |
LOG("DirectSoundCreate() SUCCEEDED.\n"); | |
if (ppDS->SetCooperativeLevel(hwnd, DSSCL_PRIORITY) == DS_OK) | |
{ | |
LOG("SetCooperativeLevel() SUCCEEDED.\n"); | |
// BUFFERDESC for primary buffer | |
DSBUFFERDESC ds_buffer_desc = {}; | |
ds_buffer_desc.dwSize = sizeof(ds_buffer_desc); | |
ds_buffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER; | |
LPDIRECTSOUNDBUFFER ppDSPrimaryBuffer; | |
// WAVEFORMATEX for primary and secondary buffers | |
WAVEFORMATEX fxFormat = {}; | |
fxFormat.wFormatTag = WAVE_FORMAT_PCM; | |
fxFormat.nChannels = 2; | |
fxFormat.nSamplesPerSec = samples_per_sec; | |
fxFormat.wBitsPerSample = 16; | |
fxFormat.nBlockAlign = (fxFormat.nChannels * fxFormat.wBitsPerSample) / 8; | |
fxFormat.nAvgBytesPerSec = samples_per_sec * fxFormat.nBlockAlign; | |
if (ppDS->CreateSoundBuffer(&ds_buffer_desc, &ppDSPrimaryBuffer, 0) == DS_OK) | |
{ | |
LOG("CreateSoundBuffer(PrimaryBuffer) SUCCEEDED.\n"); | |
if (ppDSPrimaryBuffer->SetFormat(&fxFormat) == DS_OK) | |
{ | |
// We have finally set the format of the Primary Buffer! | |
LOG("SetFormat(PrimaryBuffer) SUCCEEDED.\n"); | |
} | |
else | |
{ | |
LOG("SetFormat(PrimaryBuffer) FAILED.\n"); | |
} | |
} | |
else | |
{ | |
// TODO: diagnostic Primary CreateSoundBuffer error | |
LOG("CreateSoundBuffer(PrimaryBuffer) FAILED.\n"); | |
} | |
// BUFFERDESC for secondary buffer | |
DSBUFFERDESC ds_buffer_desc2 = {}; | |
ds_buffer_desc2.dwSize = sizeof(ds_buffer_desc2); | |
ds_buffer_desc2.dwBufferBytes = buffer_size; | |
ds_buffer_desc2.lpwfxFormat = &fxFormat; | |
if (ppDS->CreateSoundBuffer(&ds_buffer_desc2, &GlobalSecondaryBuffer, 0) == DS_OK) | |
{ | |
LOG("CreateSoundBuffer(SecondaryBuffer) SUCCEEDED.\n"); | |
} | |
else | |
{ | |
LOG("CreateSoundBuffer(SecondaryBuffer) FAILED.\n"); | |
} | |
} | |
else | |
{ | |
// TODO: diagnostic SetCooperativeLevel error | |
LOG("SetCooperativeLevel() FAILED.\n"); | |
} | |
} | |
else | |
{ | |
// TODO: diagnostic DirectSoundCreate error | |
LOG("DirectSoundCreate() FAILED.\n"); | |
} | |
} | |
else | |
{ | |
// TODO: diagnostic GetProcAddress error | |
LOG("GetProcAddress(DirectSoundCreate) FAILED.\n"); | |
} | |
} | |
else | |
{ | |
// TODO: diagnostic dsound load errors | |
LOG("dsound.dll load FAILED.\n"); | |
} | |
} | |
Win32_Window_Dimension | |
Win32_GetWindowDimension(HWND window) | |
{ | |
RECT clientRect; | |
Win32_Window_Dimension wd; | |
GetClientRect(window, &clientRect); | |
wd.Width = clientRect.right - clientRect.left; | |
wd.Height = clientRect.bottom - clientRect.top; | |
return wd; | |
} | |
internal void | |
win32_RenderWeirdGradient(const win32_offscreen_buffer * const buffer) | |
{ | |
uint8 *row = (uint8 *)buffer->Memory; | |
for (int y = 0; y < buffer->Height; ++y) | |
{ | |
uint32 *pixel = (uint32 *)row; | |
for (int x = 0; x < buffer->Width; ++x) | |
{ | |
uint32 b = (uint32)x + xOffset; | |
uint32 r = (uint32)y + yOffset; | |
*pixel++ = (r << 16) | b; | |
} | |
row += buffer->Pitch; | |
} | |
} | |
internal void | |
Win32ResizeDIBSection(win32_offscreen_buffer *buffer, int width, int height) | |
{ | |
buffer->Width = width; | |
buffer->Height = height; | |
buffer->BytesPerPixel = 4; | |
// If memory allocated previously then free it | |
if (buffer->Memory) | |
{ | |
VirtualFree( | |
buffer->Memory, // lpAddress | |
0, // dwSize | |
MEM_RELEASE // dwFreeType | |
); | |
} | |
buffer->Info.bmiHeader.biSize = sizeof(buffer->Info.bmiHeader); | |
buffer->Info.bmiHeader.biWidth = width; | |
buffer->Info.bmiHeader.biHeight = -height; // Negative means top-bottom image | |
buffer->Info.bmiHeader.biPlanes = 1; | |
buffer->Info.bmiHeader.biBitCount = 32; | |
buffer->Info.bmiHeader.biCompression = BI_RGB; | |
// Note: Thanks to Chris Hecker Ep4 5:20 for clarifying StretchDIBits | |
// can use user-allocated memory | |
SIZE_T bitmapMemorySize = (SIZE_T)buffer->BytesPerPixel * width * height; | |
buffer->Memory = VirtualAlloc( | |
0, // lpAddress | |
bitmapMemorySize, // dwSize | |
MEM_RESERVE | MEM_COMMIT, // flAllocationType | |
PAGE_READWRITE // flProtect | |
); | |
// TODO: probably clear to black | |
buffer->Pitch = width * buffer->BytesPerPixel; | |
} | |
internal void | |
Win32_DisplayBufferInWindow(HDC hdc, int window_width, int window_height, const win32_offscreen_buffer * const buffer) | |
{ | |
StretchDIBits( | |
hdc, // Dest hdc | |
0, // XDest | |
0, // YDest | |
window_width, // DestWidth | |
window_height, // DestHeight | |
0, // XSrc | |
0, // YSrc | |
buffer->Width, // SrcWidth | |
buffer->Height, // SrcHeight | |
buffer->Memory, // lpBits | |
&buffer->Info, // lpBitsInfo | |
DIB_RGB_COLORS, // iUsage | |
SRCCOPY // dwRop | |
); | |
} | |
struct win32_sound_output_t | |
{ | |
uint32 samplesPerSecond; | |
int tone_hz; | |
uint32 running_sample_index; | |
int wavePeriod; | |
//int halfWavePeriod = wavePeriod / 2; | |
int bytesPerSample; | |
int secondary_buffer_size; | |
int tone_volume; | |
}; | |
internal void | |
win32_fill_sound_buffer(win32_sound_output_t *sound_output, DWORD byte_to_lock, DWORD bytes_to_write) | |
{ | |
UNREFERENCED_PARAMETER(byte_to_lock); | |
UNREFERENCED_PARAMETER(bytes_to_write); | |
// int16 int16 int16 int16 .... | |
// [LEFT RIGHT] [LEFT RIGHT] .... | |
void *region1; | |
DWORD region1_size; | |
if(GlobalSecondaryBuffer->Lock(0, 0, ®ion1, ®ion1_size, 0, 0, DSBLOCK_ENTIREBUFFER) == DS_OK) | |
{ | |
int16 *sampleOut = (int16 *)region1; | |
int16 sampleValue; | |
for (DWORD sampleIndex = 0; sampleIndex < sound_output->samplesPerSecond; ++sampleIndex) | |
{ | |
real32 t = 2.0f * PI32 * (real32)sound_output->tone_hz / (real32)sound_output->samplesPerSecond; | |
real32 sine_value = sinf(t * sampleIndex); | |
sampleValue = (int16)(sine_value * sound_output->tone_volume); | |
*sampleOut++ = sampleValue; | |
*sampleOut++ = sampleValue; | |
} | |
GlobalSecondaryBuffer->Unlock(region1, region1_size, 0, 0); | |
} | |
} | |
LRESULT CALLBACK | |
Win32MainWindowCallback(HWND window, | |
UINT message, | |
WPARAM wParam, | |
LPARAM lParam) | |
{ | |
LRESULT result = 0; | |
switch (message) | |
{ | |
case WM_SIZE: | |
{ | |
} break; | |
case WM_ACTIVATEAPP: | |
break; | |
case WM_CLOSE: | |
GlobalRunning = false; | |
break; | |
case WM_DESTROY: | |
GlobalRunning = false; | |
fclose(f_log); | |
//PostQuitMessage(0); | |
break; | |
case WM_SYSKEYDOWN: | |
case WM_SYSKEYUP: | |
case WM_KEYDOWN: | |
{ | |
uint64 vk_code = wParam; | |
if (vk_code == 'F') | |
{ | |
GlobalSecondaryBuffer->SetCurrentPosition(0); | |
if (GlobalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING) == DS_OK) | |
{ | |
} | |
else | |
{ | |
// TODO: error playing sound | |
} | |
} | |
if (vk_code == 'G') | |
{ | |
GlobalSecondaryBuffer->Stop(); | |
} | |
} break; | |
case WM_KEYUP: | |
{ | |
uint64 vk_code = wParam; | |
bool was_down = ((lParam & (1 << 30)) != 0); | |
bool is_down = ((lParam & (1 << 31)) == 0); | |
if (vk_code == 'W') | |
{ | |
OutputDebugString(_T("W: ")); | |
if (is_down) | |
{ | |
OutputDebugString(_T("is down")); | |
xOffset += 10; | |
} | |
if (was_down) | |
{ | |
OutputDebugString(_T("was down")); | |
} | |
OutputDebugString(_T("\n")); | |
} | |
else if (vk_code == 'A') | |
{ | |
OutputDebugString(_T("A: ")); | |
if (is_down) | |
{ | |
OutputDebugString(_T("is down")); | |
} | |
if (was_down) | |
{ | |
OutputDebugString(_T("was down")); | |
} | |
OutputDebugString(_T("\n")); | |
} | |
else if (vk_code == 'S') | |
{ | |
OutputDebugString(_T("S: ")); | |
if (is_down) | |
{ | |
OutputDebugString(_T("is down")); | |
} | |
if (was_down) | |
{ | |
OutputDebugString(_T("was down")); | |
} | |
OutputDebugString(_T("\n")); | |
} | |
else if (vk_code == 'D') | |
{ | |
} | |
else if (vk_code == 'Q') | |
{ | |
} | |
else if (vk_code == 'E') | |
{ | |
} | |
else if (vk_code == 'F') | |
{ | |
GlobalSecondaryBuffer->Stop(); | |
} | |
else if (vk_code == VK_UP) | |
{ | |
} | |
else if (vk_code == VK_LEFT) | |
{ | |
} | |
else if (vk_code == VK_DOWN) | |
{ | |
} | |
else if (vk_code == VK_RIGHT) | |
{ | |
} | |
else if (vk_code == VK_ESCAPE) | |
{ | |
} | |
else if (vk_code == VK_SPACE) | |
{ | |
} | |
// Handle Windows standard combo Alt-F4 to close window and shut app | |
bool32 altKeyWasDown = lParam & (1 << 29); | |
if ((vk_code == VK_F4) && altKeyWasDown) | |
{ | |
GlobalRunning = false; | |
} | |
} break; | |
case WM_PAINT: | |
{ | |
PAINTSTRUCT paint; | |
HDC deviceContext = BeginPaint(window, &paint); | |
Win32_Window_Dimension wd = Win32_GetWindowDimension(window); | |
Win32_DisplayBufferInWindow(deviceContext, wd.Width, wd.Height, &GlobalBackBuffer); | |
EndPaint(window, &paint); | |
} break; | |
default: | |
result = DefWindowProc(window, message, wParam, lParam); | |
break; | |
} | |
return result; | |
} | |
int CALLBACK WinMain( | |
HINSTANCE hInstance, | |
HINSTANCE , // removed unreferenced formal parameter hPrevInstance | |
LPSTR , // removed unreferenced formal parameter lpCmdLine | |
int nCmdShow) | |
{ | |
WNDCLASSEX windowClass = {}; // initialise to zero | |
windowClass.cbSize = sizeof(WNDCLASSEX); | |
windowClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW; | |
windowClass.lpfnWndProc = Win32MainWindowCallback; | |
windowClass.hInstance = hInstance; | |
//windowClass.hIcon; | |
windowClass.lpszClassName = _T("windowClass"); | |
if (!RegisterClassEx(&windowClass)) | |
{ | |
MessageBox(NULL, | |
_T("Call to RegisterClassEx failed!"), | |
szTitle, | |
NULL); | |
return 1; | |
} | |
HWND window = CreateWindowEx( | |
0, // dwExStyle | |
windowClass.lpszClassName, // lpClassName | |
szTitle, // lpWindowName | |
WS_OVERLAPPEDWINDOW, // dwStyle | |
CW_USEDEFAULT, // x | |
CW_USEDEFAULT, // y | |
CW_USEDEFAULT, // nWidth | |
CW_USEDEFAULT, // nHeight | |
0, // hWndParent | |
0, // hMenu | |
hInstance, // hInstance | |
0 // lpParam | |
); | |
if (!window) | |
{ | |
MessageBox(NULL, | |
_T("Call to CreateWindowEx failed!"), | |
szTitle, | |
NULL); | |
return 1; | |
} | |
// If we get this far we are good to go! | |
GlobalRunning = true; | |
win32_init_logging(); | |
// LOG("log file open SUCCEEDED\n"); | |
win32_sound_output_t sound_output = {}; | |
sound_output.samplesPerSecond = 44100; | |
sound_output.tone_hz = 400; | |
sound_output.running_sample_index = 0; | |
sound_output.wavePeriod = sound_output.samplesPerSecond / sound_output.tone_hz; | |
sound_output.bytesPerSample = sizeof(int16)*2; | |
sound_output.secondary_buffer_size = sound_output.samplesPerSecond * sound_output.bytesPerSample; | |
sound_output.tone_volume = 3000; | |
win32_load_XInput(); | |
win32_init_dsound(window, sound_output.samplesPerSecond, sound_output.secondary_buffer_size); | |
win32_fill_sound_buffer(&sound_output, 0, sound_output.secondary_buffer_size); | |
HDC win_dc = GetDC(window); // Get DC once, as we are using CS_OWNDC | |
//Set up back buffer to a fixed size | |
Win32ResizeDIBSection(&GlobalBackBuffer, 1280, 720); | |
ShowWindow(window, nCmdShow); | |
UpdateWindow(window); | |
while (GlobalRunning) | |
{ | |
MSG msg; | |
while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) | |
{ | |
if (msg.message == WM_QUIT) | |
{ | |
GlobalRunning = false; | |
} | |
TranslateMessage(&msg); | |
DispatchMessage(&msg); | |
} | |
// Poll for XBOX gamepad input | |
// TODO: should we poll more frequently? | |
for (DWORD i = 0; i < XUSER_MAX_COUNT; ++i) | |
{ | |
XINPUT_STATE controllerState; | |
if (XInputGetState(i, &controllerState) == ERROR_SUCCESS) | |
{ | |
//TODO: controller is plugged in | |
//TODO: see if controllerState.dwPacketNumber increments too rapidly | |
// Also see Ep 7 13:55 about an XInput performance issue | |
// XINPUT_GAMEPAD *pad = &controllerState.Gamepad; | |
// bool up = pad->wButtons & XINPUT_GAMEPAD_DPAD_UP; | |
// bool down = pad->wButtons & XINPUT_GAMEPAD_DPAD_DOWN; | |
// bool left = pad->wButtons & XINPUT_GAMEPAD_DPAD_LEFT; | |
// bool right = pad->wButtons & XINPUT_GAMEPAD_DPAD_RIGHT; | |
// bool start = pad->wButtons & XINPUT_GAMEPAD_START; | |
// bool back = pad->wButtons & XINPUT_GAMEPAD_BACK; | |
// bool leftSHoulder = pad->wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER; | |
// bool rightShoulder = pad->wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER; | |
// bool aButton = pad->wButtons & XINPUT_GAMEPAD_A; | |
// bool bButton = pad->wButtons & XINPUT_GAMEPAD_B; | |
// bool xButton = pad->wButtons & XINPUT_GAMEPAD_X; | |
// bool yButton = pad->wButtons & XINPUT_GAMEPAD_Y; | |
// int16 stickX = pad->sThumbLX; | |
// int16 stickY = pad->sThumbLY; | |
} | |
else | |
{ | |
//TODO: controller is not plugged in | |
} | |
} | |
win32_RenderWeirdGradient(&GlobalBackBuffer); | |
Win32_Window_Dimension wd = Win32_GetWindowDimension(window); | |
Win32_DisplayBufferInWindow(win_dc, wd.Width,wd.Height, &GlobalBackBuffer); | |
++xOffset; | |
++yOffset; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment