452 lines
17 KiB
C++
452 lines
17 KiB
C++
// Windows and Windows DLLs
|
|
#include <windows.h>
|
|
#include <Xinput.h>
|
|
#include <dsound.h>
|
|
|
|
#include "handmade.cpp"
|
|
|
|
struct Win32OffscreenBuffer {
|
|
BITMAPINFO info;
|
|
void *memory;
|
|
int width;
|
|
int height;
|
|
int bytesPerPixel;
|
|
};
|
|
|
|
struct Win32WindowDimensions {
|
|
int width;
|
|
int height;
|
|
};
|
|
|
|
global ATOM HH_CTRLW;
|
|
global bool running;
|
|
global Win32OffscreenBuffer globalBackBuffer;
|
|
global LPDIRECTSOUNDBUFFER globalSecondaryBuffer;
|
|
|
|
// XInputGetState
|
|
#define X_INPUT_GET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
|
|
typedef X_INPUT_GET_STATE(XInputGetStateFn);
|
|
X_INPUT_GET_STATE(XInputGetStateStub) { return ERROR_DEVICE_NOT_CONNECTED; }
|
|
global XInputGetStateFn *XInputGetStateDyn = XInputGetStateStub;
|
|
#define XInputGetState XInputGetStateDyn
|
|
|
|
// XInputSetState
|
|
#define X_INPUT_SET_STATE(name) DWORD WINAPI name(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration)
|
|
typedef X_INPUT_SET_STATE(XInputSetStateFn);
|
|
X_INPUT_SET_STATE(XInputSetStateStub) { return ERROR_DEVICE_NOT_CONNECTED; }
|
|
global XInputSetStateFn *XInputSetStateDyn = XInputSetStateStub;
|
|
#define XInputSetState XInputSetStateDyn
|
|
|
|
typedef HRESULT WINAPI DirectSoundCreateFn(LPCGUID pcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter);
|
|
|
|
#define stackAlloc(size, type) (type*)_alloca(size*sizeof(type))
|
|
|
|
internal void resizeDIBSection(Win32OffscreenBuffer *buffer, int width, int height) {
|
|
if (buffer->memory) {
|
|
VirtualFree(buffer->memory, NULL, MEM_RELEASE);
|
|
}
|
|
|
|
buffer->width = width;
|
|
buffer->height = height;
|
|
|
|
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;
|
|
|
|
int bitmapSize = buffer->width*buffer->height*4;
|
|
buffer->memory = VirtualAlloc(NULL, bitmapSize, MEM_COMMIT, PAGE_READWRITE);
|
|
}
|
|
|
|
internal Win32WindowDimensions win32GetWindowDimensions(HWND window) {
|
|
Win32WindowDimensions result;
|
|
RECT clientRect;
|
|
GetClientRect(window, &clientRect);
|
|
result.width = clientRect.right - clientRect.left;
|
|
result.height = clientRect.bottom - clientRect.top;
|
|
return result;
|
|
}
|
|
|
|
internal void win32DrawBufferInWindow(Win32OffscreenBuffer *buffer, HWND window) {
|
|
Win32WindowDimensions winDims = win32GetWindowDimensions(window);
|
|
StretchDIBits(
|
|
GetDC(window),
|
|
0, 0, winDims.width, winDims.height,
|
|
0, 0, buffer->width, buffer->height,
|
|
buffer->memory,
|
|
&buffer->info,
|
|
DIB_RGB_COLORS,
|
|
SRCCOPY
|
|
);
|
|
}
|
|
|
|
internal void win32LoadXInput() {
|
|
HMODULE xInputLib = LoadLibrary("xinput1_4.dll");
|
|
if (!xInputLib) {
|
|
xInputLib = LoadLibrary("xinput1_3.dll");
|
|
}
|
|
|
|
if (xInputLib) {
|
|
XInputGetState = (XInputGetStateFn *)GetProcAddress(xInputLib, "XInputGetState");
|
|
XInputSetState = (XInputSetStateFn *)GetProcAddress(xInputLib, "XInputSetState");
|
|
}
|
|
}
|
|
|
|
internal void win32InitSound(HWND window, int32 samplesPerSec, int bufferSize) {
|
|
HMODULE dSoundLibrary = LoadLibraryA("dsound.dll");
|
|
if (dSoundLibrary) {
|
|
DirectSoundCreateFn *DirectSoundCreate = (DirectSoundCreateFn*)GetProcAddress(dSoundLibrary, "DirectSoundCreate");
|
|
LPDIRECTSOUND DirectSound;
|
|
if (DirectSoundCreate && SUCCEEDED(DirectSoundCreate(0, &DirectSound, 0))) {
|
|
WAVEFORMATEX waveFormat = {};
|
|
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
|
waveFormat.nChannels = 2;
|
|
waveFormat.nSamplesPerSec = samplesPerSec;
|
|
waveFormat.wBitsPerSample = 16;
|
|
waveFormat.nBlockAlign = (waveFormat.nChannels*waveFormat.wBitsPerSample) / 8;
|
|
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec*waveFormat.nBlockAlign;
|
|
waveFormat.cbSize = 0;
|
|
|
|
if (SUCCEEDED(DirectSound->SetCooperativeLevel(window, DSSCL_PRIORITY))) {
|
|
DSBUFFERDESC bufferDescription = {};
|
|
bufferDescription.dwSize = sizeof(bufferDescription);
|
|
bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
|
|
|
LPDIRECTSOUNDBUFFER primaryBuffer;
|
|
if (SUCCEEDED(DirectSound->CreateSoundBuffer(&bufferDescription, &primaryBuffer, 0))) {
|
|
if (SUCCEEDED(primaryBuffer->SetFormat(&waveFormat))) {
|
|
OutputDebugStringA("Primary buffer format was set.\n");
|
|
} else {
|
|
//No sound! (?)
|
|
}
|
|
}
|
|
} else {
|
|
// No sound! (?)
|
|
}
|
|
|
|
DSBUFFERDESC bufferDescription = {};
|
|
bufferDescription.dwSize = sizeof(bufferDescription);
|
|
bufferDescription.dwFlags = 0;
|
|
bufferDescription.dwBufferBytes = bufferSize;
|
|
bufferDescription.lpwfxFormat = &waveFormat;
|
|
|
|
if (SUCCEEDED(DirectSound->CreateSoundBuffer(&bufferDescription, &globalSecondaryBuffer, 0))) {
|
|
OutputDebugStringA("Secondary buffer created successfully!\n");
|
|
}
|
|
} else {
|
|
// No sound! (?)
|
|
}
|
|
}
|
|
}
|
|
|
|
LRESULT mainWindowCallback(
|
|
HWND window,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
) {
|
|
LRESULT result = 0;
|
|
switch (message) {
|
|
case WM_SIZE: {
|
|
} break;
|
|
|
|
case WM_DESTROY: {
|
|
OutputDebugStringA("WM_DESTROY\n");
|
|
} break;
|
|
|
|
case WM_CLOSE: {
|
|
running = false;
|
|
} break;
|
|
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT paint = {};
|
|
HDC deviceContext = BeginPaint(window, &paint);
|
|
int x = paint.rcPaint.left;
|
|
int y = paint.rcPaint.top;
|
|
int width = paint.rcPaint.right - paint.rcPaint.left;
|
|
int height = paint.rcPaint.bottom - paint.rcPaint.top;
|
|
win32DrawBufferInWindow(&globalBackBuffer, window);
|
|
EndPaint(window, &paint);
|
|
} break;
|
|
|
|
case WM_ACTIVATEAPP: {
|
|
} break;
|
|
|
|
case WM_HOTKEY: {
|
|
if (wParam == HH_CTRLW) {
|
|
running = false;
|
|
}
|
|
} break;
|
|
|
|
case WM_SYSKEYDOWN:
|
|
case WM_SYSKEYUP:
|
|
case WM_KEYUP:
|
|
case WM_KEYDOWN: {
|
|
uint32 VKCode = wParam;
|
|
bool wasDown = (lParam & (1 << 30)) != 0;
|
|
bool isDown = (lParam & (1 << 31)) == 0;
|
|
if (wasDown != isDown) {
|
|
if (VKCode == 'W') {
|
|
} else if (VKCode == 'A') {
|
|
} else if (VKCode == 'S') {
|
|
} else if (VKCode == 'D') {
|
|
} else if (VKCode == 'Q') {
|
|
} else if (VKCode == 'E') {
|
|
} else if (VKCode == VK_ESCAPE) {
|
|
} else if (VKCode == VK_UP) {
|
|
} else if (VKCode == VK_LEFT) {
|
|
} else if (VKCode == VK_DOWN) {
|
|
} else if (VKCode == VK_RIGHT) {
|
|
} else if (VKCode == VK_SPACE) {
|
|
}
|
|
}
|
|
|
|
bool32 altKeyWasDown = lParam & (1 << 29);
|
|
if (altKeyWasDown && VKCode == VK_F4) {
|
|
running = false;
|
|
}
|
|
} break;
|
|
|
|
default: {
|
|
result = DefWindowProcA(window, message, wParam, lParam);
|
|
} break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct Win32SoundOutput {
|
|
int samplesPerSecond;
|
|
int toneHz;
|
|
int wavePeriod;
|
|
int16 toneVolume;
|
|
uint32 runningSampleIndex;
|
|
int bytesPerSample;
|
|
int secondaryBufferSize;
|
|
real32 tSine;
|
|
int latencySampleCount;
|
|
};
|
|
|
|
internal void win32ClearBuffer(Win32SoundOutput *soundOutput) {
|
|
VOID *region1;
|
|
DWORD region1Size;
|
|
VOID *region2;
|
|
DWORD region2Size;
|
|
HRESULT lock = globalSecondaryBuffer->Lock(0, soundOutput->secondaryBufferSize, ®ion1, ®ion1Size, ®ion2, ®ion2Size, 0);
|
|
if (SUCCEEDED(lock)) {
|
|
int16 *destSamples = (int16*)region1;
|
|
for (DWORD byteIndex = 0; byteIndex < region1Size; byteIndex++) {
|
|
// *destSamples++ = 0;
|
|
}
|
|
destSamples = (int16*)region2;
|
|
for (DWORD byteIndex = 0; byteIndex < region2Size; byteIndex++) {
|
|
//*destSamples++ = 0;
|
|
}
|
|
globalSecondaryBuffer->Unlock(region1, region1Size, region2, region2Size);
|
|
} else {
|
|
// no lock!
|
|
}
|
|
}
|
|
|
|
internal void win32FillSoundBuffer(Win32SoundOutput *soundOutput, DWORD byteToLock, DWORD bytesToWrite, GameSoundOutputBuffer *sourceBuffer) {
|
|
VOID *region1;
|
|
DWORD region1Size;
|
|
VOID *region2;
|
|
DWORD region2Size;
|
|
HRESULT lock = globalSecondaryBuffer->Lock(byteToLock, bytesToWrite, ®ion1, ®ion1Size, ®ion2, ®ion2Size, 0);
|
|
if (SUCCEEDED(lock)) {
|
|
DWORD region1SampleCount = region1Size/soundOutput->bytesPerSample;
|
|
int16 *destSamples = (int16*)region1;
|
|
int16 *sourceSamples = sourceBuffer->samples;
|
|
for (DWORD sampleIndex = 0; sampleIndex < region1SampleCount; sampleIndex++) {
|
|
*destSamples++ = *sourceSamples++;
|
|
*destSamples++ = *sourceSamples++;
|
|
soundOutput->runningSampleIndex++;
|
|
}
|
|
DWORD region2SampleCount = region2Size/soundOutput->bytesPerSample;
|
|
destSamples = (int16*)region2;
|
|
for (DWORD sampleIndex = 0; sampleIndex < region2SampleCount; sampleIndex++) {
|
|
*destSamples++ = *sourceSamples++;
|
|
*destSamples++ = *sourceSamples++;
|
|
soundOutput->runningSampleIndex++;
|
|
}
|
|
globalSecondaryBuffer->Unlock(region1, region1Size, region2, region2Size);
|
|
} else {
|
|
// no lock!
|
|
}
|
|
}
|
|
|
|
int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLine, int commandShow) {
|
|
LARGE_INTEGER performanceFrequencyResult;
|
|
QueryPerformanceFrequency(&performanceFrequencyResult);
|
|
int64 performanceFrequency = performanceFrequencyResult.QuadPart;
|
|
|
|
WNDCLASSA windowClass = {};
|
|
windowClass.style = CS_VREDRAW | CS_HREDRAW;
|
|
windowClass.lpfnWndProc = &mainWindowCallback;
|
|
windowClass.hInstance = instance;
|
|
windowClass.lpszClassName = "HandmadeHeroWindowClass";
|
|
|
|
resizeDIBSection(&globalBackBuffer, 1280, 720);
|
|
|
|
if (RegisterClass(&windowClass)) {
|
|
HWND window = CreateWindowExA(
|
|
NULL,
|
|
windowClass.lpszClassName,
|
|
"Handmade Hero",
|
|
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
NULL,
|
|
NULL,
|
|
instance,
|
|
NULL
|
|
);
|
|
if (window) {
|
|
HH_CTRLW = GlobalAddAtomA("HH_CTRLW");
|
|
RegisterHotKey(window, HH_CTRLW, MOD_CONTROL, 'W');
|
|
|
|
MSG message;
|
|
running = true;
|
|
|
|
win32LoadXInput();
|
|
|
|
// graphics test
|
|
int xOffset = 0;
|
|
int yOffset = 0;
|
|
|
|
// sound test
|
|
Win32SoundOutput soundOutput = {};
|
|
soundOutput.samplesPerSecond = 48000;
|
|
soundOutput.toneHz = 440; // A above middle C
|
|
soundOutput.wavePeriod = soundOutput.samplesPerSecond / soundOutput.toneHz;
|
|
soundOutput.toneVolume = 6000;
|
|
soundOutput.runningSampleIndex = 0;
|
|
soundOutput.bytesPerSample = sizeof(int16)*2;
|
|
soundOutput.secondaryBufferSize = soundOutput.samplesPerSecond*soundOutput.bytesPerSample;
|
|
soundOutput.latencySampleCount = soundOutput.samplesPerSecond / 15.0f;
|
|
|
|
int16 *samples = (int16*)VirtualAlloc(NULL, soundOutput.secondaryBufferSize, MEM_COMMIT, PAGE_READWRITE);
|
|
|
|
win32InitSound(window, soundOutput.samplesPerSecond, soundOutput.secondaryBufferSize);
|
|
win32ClearBuffer(&soundOutput);
|
|
globalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
|
|
|
LARGE_INTEGER lastCounter;
|
|
QueryPerformanceCounter(&lastCounter);
|
|
int64 lastCycleCount = __rdtsc();
|
|
while (running) {
|
|
while (PeekMessageA(&message, NULL, NULL, NULL, PM_REMOVE)) {
|
|
if (message.message == WM_QUIT) {
|
|
running = false;
|
|
}
|
|
TranslateMessage(&message);
|
|
DispatchMessage(&message);
|
|
}
|
|
|
|
XINPUT_STATE controllerState;
|
|
for (DWORD controllerIndex = 0; controllerIndex < XUSER_MAX_COUNT; controllerIndex++) {
|
|
if (XInputGetState(controllerIndex, &controllerState) == ERROR_SUCCESS) {
|
|
break;
|
|
} else {
|
|
// controller not available
|
|
}
|
|
}
|
|
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;
|
|
|
|
float stickYNorm = 0;
|
|
float stickXNorm = 0;
|
|
if (stickY != 0 && abs(stickY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
|
|
stickYNorm = stickY / 32767.0;
|
|
}
|
|
if (stickX != 0 && abs(stickX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) {
|
|
stickXNorm = stickX / 32767.0;
|
|
}
|
|
|
|
float factor = leftShoulder ? 2.5 : 1.0;
|
|
yOffset += stickY/5000.0 * factor;
|
|
xOffset -= stickX/5000.0 * factor;
|
|
|
|
soundOutput.toneHz = 220 + (stickYNorm + 1.0)/2.0 * 220.0;
|
|
soundOutput.wavePeriod = soundOutput.samplesPerSecond / soundOutput.toneHz;
|
|
|
|
GameOffscreenBuffer videoBuffer = {};
|
|
videoBuffer.memory = globalBackBuffer.memory;
|
|
videoBuffer.width = globalBackBuffer.width;
|
|
videoBuffer.height = globalBackBuffer.height;
|
|
|
|
GameInput input = {};
|
|
input.xOffset = xOffset;
|
|
input.yOffset = yOffset;
|
|
|
|
// Sound test
|
|
DWORD playCursor;
|
|
DWORD writeCursor;
|
|
DWORD byteToLock;
|
|
DWORD targetCursor;
|
|
DWORD bytesToWrite;
|
|
bool soundIsValid = true;
|
|
if (SUCCEEDED(globalSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor))) {
|
|
byteToLock = (soundOutput.runningSampleIndex*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize;
|
|
targetCursor = (playCursor + soundOutput.latencySampleCount*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize;
|
|
if (byteToLock == targetCursor) {
|
|
bytesToWrite = 0;
|
|
} else if (byteToLock > targetCursor) {
|
|
bytesToWrite = soundOutput.secondaryBufferSize - byteToLock + targetCursor;
|
|
} else {
|
|
bytesToWrite = targetCursor - byteToLock;
|
|
}
|
|
soundIsValid = true;
|
|
}
|
|
|
|
GameSoundOutputBuffer soundBuffer = {};
|
|
soundBuffer.samplesPerSecond = soundOutput.samplesPerSecond;
|
|
soundBuffer.sampleCount = bytesToWrite / soundOutput.bytesPerSample;
|
|
soundBuffer.samples = samples;
|
|
|
|
gameUpdateAndRender(&videoBuffer, &input, &soundBuffer);
|
|
|
|
if (soundIsValid) {
|
|
win32FillSoundBuffer(&soundOutput, byteToLock, bytesToWrite, &soundBuffer);
|
|
}
|
|
win32DrawBufferInWindow(&globalBackBuffer, window);
|
|
|
|
LARGE_INTEGER endCounter;
|
|
QueryPerformanceCounter(&endCounter);
|
|
int64 endCycleCount = __rdtsc();
|
|
int32 cyclesElapsed = endCycleCount - lastCycleCount;
|
|
lastCycleCount = endCycleCount;
|
|
|
|
int64 counterElapsed = endCounter.QuadPart - lastCounter.QuadPart;
|
|
real32 msElapsed = (real32)(1000.0f*counterElapsed) / performanceFrequency;
|
|
real32 fps = (real32)(1000.0f*performanceFrequency/(real32)counterElapsed)/1000.0f;
|
|
debug_printf(L"%f ms, %f fps, %f Mcl, %fGHz\n", msElapsed, fps, cyclesElapsed / 1000000.0f, (cyclesElapsed/(msElapsed*1000))/1000000.0f);
|
|
lastCounter = endCounter;
|
|
}
|
|
} else {
|
|
// failed
|
|
}
|
|
} else {
|
|
// failed
|
|
}
|
|
return(0);
|
|
}
|
|
|