// Windows and Windows DLLs #include #include #include #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); }