#include #include #include #include #include #include #include #include #define internal static // for functions #define local_persist static // for static variables in a scope #define global static // for global variables 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; struct Win32OffscreenBuffer { BITMAPINFO info; void *memory; int width; int height; int bytesPerPixel; }; struct Win32WindowDimensions { int width; int height; }; #define PI32 3.141592653589f global ATOM HH_CTRLW; global bool running; global Win32OffscreenBuffer globalBackBuffer; global LPDIRECTSOUNDBUFFER globalSecondaryBuffer; void debug_printf(char* format, ...) { int bufsize = strlen(format)*10; char *output = (char*)malloc(bufsize); va_list list; va_start(list, format); vsprintf_s(output, bufsize, format, list); OutputDebugStringA(output); free(output); } // 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); internal void renderWeirdGradient(Win32OffscreenBuffer *buffer, int xOffset, int yOffset) { int bytesPerPixel = 4; int pitch = buffer->width*bytesPerPixel; 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++) { uint8 blue = x + xOffset; uint8 green = y + yOffset; *pixel++ = (green << 8) | blue; } row += pitch; } } 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->bytesPerPixel = 4; 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*buffer->bytesPerPixel; 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; }; internal void win32FillSoundBuffer(Win32SoundOutput *soundOutput, DWORD byteToLock, DWORD bytesToWrite) { 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 *sampleOut = (int16*)region1; for (DWORD sampleIndex = 0; sampleIndex < region1SampleCount; sampleIndex++) { real32 t = 2.0f * PI32 * (real32)soundOutput->runningSampleIndex / (real32)soundOutput->wavePeriod; int16 sampleValue = (int16)(sin(t) * (real32)soundOutput->toneVolume); *sampleOut++ = sampleValue; *sampleOut++ = sampleValue; soundOutput->runningSampleIndex++; } DWORD region2SampleCount = region2Size/soundOutput->bytesPerSample; sampleOut = (int16*)region2; for (DWORD sampleIndex = 0; sampleIndex < region2SampleCount; sampleIndex++) { real32 t = 2.0f * PI32 * (real32)soundOutput->runningSampleIndex / (real32)soundOutput->wavePeriod; int16 sampleValue = (int16)(sin(t) * soundOutput->toneVolume); *sampleOut++ = sampleValue; *sampleOut++ = sampleValue; soundOutput->runningSampleIndex++; } globalSecondaryBuffer->Unlock(region1, region1Size, region2, region2Size); } else { OutputDebugStringA("No lock!\n"); } } int APIENTRY WinMain( HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLine, int commandShow ) { win32LoadXInput(); 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; // 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; win32InitSound(window, soundOutput.samplesPerSecond, soundOutput.secondaryBufferSize); win32FillSoundBuffer(&soundOutput, 0, soundOutput.secondaryBufferSize); globalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING); 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; } yOffset += stickY/5000; xOffset -= stickX/5000; renderWeirdGradient(&globalBackBuffer, xOffset, yOffset); // Sound test DWORD playCursor; DWORD writeCursor; if (SUCCEEDED(globalSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor))) { DWORD byteToLock = (soundOutput.runningSampleIndex*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize; DWORD bytesToWrite; if (byteToLock == playCursor) { bytesToWrite = 0; } else if (byteToLock > playCursor) { bytesToWrite = soundOutput.secondaryBufferSize - byteToLock + playCursor; } else { bytesToWrite = playCursor - byteToLock; } win32FillSoundBuffer(&soundOutput, byteToLock, bytesToWrite); } win32DrawBufferInWindow(&globalBackBuffer, window); } } else { // failed } } else { // failed } return(0); }