Skip to content

Instantly share code, notes, and snippets.

@mrennix
Created August 13, 2017 13:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mrennix/60384f0e86a655ba8180fa8a6c7cb3ca to your computer and use it in GitHub Desktop.
Save mrennix/60384f0e86a655ba8180fa8a6c7cb3ca to your computer and use it in GitHub Desktop.
Handmade Hero Directsound clicking
#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, &region1, &region1_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