Skip to content

Instantly share code, notes, and snippets.

@nahuakang
Last active January 4, 2025 23:22
Show Gist options
  • Save nahuakang/64aa76bb1de2839ccd1d3beb8022cc31 to your computer and use it in GitHub Desktop.
Save nahuakang/64aa76bb1de2839ccd1d3beb8022cc31 to your computer and use it in GitHub Desktop.
Handmade Hero in Jai on Day 6
#import "Basic";
#import "Windows";
#import "Windows_Utf8";
#import "Gamepad";
//#import "Gamepad/Xinput"; // DON'T IMPORT THIS!
user32 :: #system_library "user32";
gdi :: #system_library "Gdi32";
/*
Win32: Missing Virtual-Key Codes
https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
*/
VK_A :: 0x41;
VK_D :: 0x44;
VK_E :: 0x45;
VK_Q :: 0x51;
VK_S :: 0x53;
VK_W :: 0x57;
/*
Win32: Missing winerror
https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
*/
ERROR_SUCCESS :: 0x0;
/*
Win32: Missing structs
*/
PAINTSTRUCT :: struct {
hdc : HDC;
fErase : BOOL;
rcPaint : RECT;
fRestore : BOOL;
fIncUpdate : BOOL;
rgbReserved : [32]u8;
}
/*
Win32 & GDI: Missing procedures.
Read jai/modules/Windows.jai for undertanding.
*/
BeginPaint :: (hWnd: HWND, lpPaint: *PAINTSTRUCT) -> HDC #foreign user32;
CreateDIBSection :: (hdc: HDC, pbmi: *BITMAPINFO, usage: u32, ppvBits: **void, hSection: HANDLE, offset: DWORD) -> HBITMAP #foreign gdi;
EndPaint :: (hWnd: HWND, lpPaint: *PAINTSTRUCT) -> HDC #foreign user32;
GetMessageA :: (msg: *MSG, hWnd: HWND, wMsgFilterMin: u32, wMsgFilterMax: u32) -> BOOL #foreign user32;
PatBlt :: (hdc: HDC, x: s32, y: s32, w: s32, h: s32, rop: DWORD) -> BOOL #foreign gdi;
StretchDIBits :: (hdc: HDC, xDest: s32, yDest: s32, DestWidth: s32, DestHeight: s32, xSrc: s32, ySrc: s32, SrcWidth: s32, SrcHeight: s32, lpBits: *void, lpbmi: *BITMAPINFO, iUsage: u32, rop: ROP) -> s32 #foreign gdi;
/*
Handmade Hero Structs
*/
Win32OffscreenBuffer :: struct {
// NOTE(nahua) : Pixels are always 32 bits wide
// Memory Order : 0x BB GG RR xx
// Little Endian : 0x xx RR GG BB
info : BITMAPINFO;
memory : *void;
width : s32;
height : s32;
pitch : s32;
bytes_per_pixel : s32;
}
Win32WindowDimension :: struct {
width : s32;
height : s32;
}
/*
Handmade Hero Variables
*/
global_back_buffer: Win32OffscreenBuffer;
global_running: bool;
/*
Handmade Hero Constants
*/
WINDOW_CLASS_NAME :: "HandmadeHeroWindowClass";
WINDOW_NAME :: "Handmade Hero";
/*
Handmade Hero System-Related Procedures
*/
render_weird_gradient :: (buffer: *Win32OffscreenBuffer, x_offset: s32, y_offset: s32) {
row := cast(*u8) buffer.memory;
for y: 0..buffer.height-1 {
pixel := cast(*u32) row;
for x: 0..buffer.width-1 {
// Memory: BB GG RR XX
// Register: 0x XX RR GG BB
red : u8 = 0;
green : u8 = cast,no_check(u8) (y + y_offset); // need cast,no_check(u8) instead of xx
blue : u8 = cast,no_check(u8) (x + x_offset);
pixel.* = (cast(u32)red) << 16 | (cast(u32)green) << 8 | cast(u32)blue;
pixel += 1;
}
row += buffer.pitch;
}
}
win32_get_window_dimension :: (window: HWND) -> Win32WindowDimension {
result: Win32WindowDimension;
client_rect: RECT;
GetClientRect(window, *client_rect);
result.width = client_rect.right - client_rect.left;
result.height = client_rect.bottom - client_rect.top;
return result;
}
win32_resize_dib_section :: (buffer: *Win32OffscreenBuffer, width: s32, height: s32) {
if buffer.memory {
VirtualFree(buffer.memory, 0, MEM_RELEASE);
}
buffer.width = width;
buffer.height = height;
buffer.bytes_per_pixel = 4;
buffer.pitch = buffer.width * buffer.bytes_per_pixel;
buffer.info.bmiHeader.biSize = size_of(BITMAPINFOHEADER);
buffer.info.bmiHeader.biWidth = buffer.width;
buffer.info.bmiHeader.biHeight = -buffer.height; // Negative value for top-down pitch
buffer.info.bmiHeader.biPlanes = 1;
buffer.info.bmiHeader.biBitCount = 32;
buffer.info.bmiHeader.biCompression = BI_RGB;
bitmap_memory_size := buffer.bytes_per_pixel * (buffer.width * buffer.height);
buffer.memory = VirtualAlloc(null, xx bitmap_memory_size, MEM_COMMIT, PAGE_READWRITE);
}
win32_display_buffer_window :: (device_context: HDC, window_width: s32, window_height: s32, buffer: *Win32OffscreenBuffer) {
StretchDIBits(device_context,
0, 0, window_width, window_height, // Destination (window)
0, 0, buffer.width, buffer.height, // Source (bitmap buffer)
buffer.memory,
*buffer.info,
DIB_RGB_COLORS,
ROP.SRCCOPY);
}
/*
win32_window_callback corresponds to Win32MainWindowCallback.
*/
win32_window_callback :: (window: HWND, message: u32, wParam: WPARAM, lParam: LPARAM) -> LRESULT #c_call {
result: LRESULT = 0;
if message == {
case WM_ACTIVATEAPP;
OutputDebugStringA("WM_ACTIVATEAPP\n");
case WM_CLOSE;
global_running = false;
case WM_DESTROY;
global_running = false;
case WM_SYSKEYDOWN; #through;
case WM_SYSKEYUP; #through;
case WM_KEYDOWN; #through;
case WM_KEYUP;
is_down := ((lParam & (1 << 31)) == 0);
was_down := ((lParam & (1 << 30)) != 0);
vk_code := wParam; // u64
if is_down != was_down {
if vk_code == VK_W OutputDebugStringA("Pressed W\n");
else if vk_code == VK_A OutputDebugStringA("Pressed A\n");
else if vk_code == VK_S OutputDebugStringA("Pressed S\n");
else if vk_code == VK_D OutputDebugStringA("Pressed D\n");
else if vk_code == VK_Q OutputDebugStringA("Pressed Q\n");
else if vk_code == VK_E OutputDebugStringA("Pressed E\n");
else if vk_code == VK_UP OutputDebugStringA("Pressed UP\n");
else if vk_code == VK_DOWN OutputDebugStringA("Pressed DOWN\n");
else if vk_code == VK_LEFT OutputDebugStringA("Pressed LEFT\n");
else if vk_code == VK_RIGHT OutputDebugStringA("Pressed RIGHT\n");
else if vk_code == VK_ESCAPE {
OutputDebugStringA("ESCAPE: ");
if is_down OutputDebugStringA("IsDown ");
if was_down OutputDebugStringA("WasDown");
OutputDebugStringA("\n");
}
else if vk_code == VK_SPACE OutputDebugStringA("PRESSED SPACE\n");
alt_key_was_down := (lParam & (1 << 29));
if vk_code == VK_F4 && alt_key_was_down {
global_running = false;
}
}
case WM_PAINT;
paint: PAINTSTRUCT;
device_context := BeginPaint(window, *paint);
// Native procedure in #c_call requires push_context
push_context{
dimension := win32_get_window_dimension(window);
win32_display_buffer_window(device_context,
dimension.width,
dimension.height,
*global_back_buffer);
}
EndPaint(window, *paint);
case;
result = DefWindowProcW(window, message, wParam, lParam);
}
return result;
}
/*
main corresponds to WinMain.
*/
main :: () {
init_gamepad();
window_class: WNDCLASSEXW;
window_class.cbSize = size_of(WNDCLASSEXW);
window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.lpfnWndProc = xx win32_window_callback;
window_class.hInstance = GetModuleHandleW(null);
window_class.lpszClassName = utf8_to_wide(WINDOW_CLASS_NAME);
win32_resize_dib_section(*global_back_buffer, 1280, 720);
if RegisterClassExW(*window_class) {
window := CreateWindowExW(0,
window_class.lpszClassName,
utf8_to_wide(WINDOW_NAME),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
null,
null,
window_class.hInstance,
null);
if window {
// NOTE(nahua): Since we specified CS_OWNDC, we can just get one
// device context and use it forever because we're not sharing it.
device_context := GetDC(window);
x_offset: s32 = 0;
y_offset: s32 = 0;
global_running = true;
while global_running {
message: MSG;
if PeekMessageA(*message, null, 0, 0, PM_REMOVE) != 0 {
if message.message == WM_QUIT global_running = false;
TranslateMessage(*message);
DispatchMessageA(*message);
}
for controller_index: 0..XUSER_MAX_COUNT {
state: XINPUT_STATE;
result := XInputGetState(0, *state);
if result == ERROR_SUCCESS {
// NOTE(nahua): This controller is plugged in and available
buttons := state.Gamepad.wButtons;
up := (buttons & XINPUT_GAMEPAD_DPAD_UP) != 0;
down := (buttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
left := (buttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
right := (buttons & XINPUT_GAMEPAD_DRAD_RIGHT) != 0;
start := (buttons & XINPUT_GAMEPAD_START) != 0;
back := (buttons & XINPUT_GAMEPAD_BACK) != 0;
left_shoulder := (buttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
right_shoulder := (buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
a := (buttons & XINPUT_GAMEPAD_A) != 0;
b := (buttons & XINPUT_GAMEPAD_B) != 0;
x := (buttons & XINPUT_GAMEPAD_X) != 0;
y := (buttons & XINPUT_GAMEPAD_Y) != 0;
pad := state.Gamepad;
stick_x := pad.sThumbLX;
stick_y := pad.sThumbLY;
x_offset += stick_x >> 12;
y_offset += stick_y >> 12;
} else {
// NOTE(nahua): This controller is not available
}
}
vibration: XINPUT_VIBRATION;
vibration.wLeftMotorSpeed = 60000; // u16
vibration.wRightMotorSpeed = 60000; // u16
XInputSetState(0, *vibration);
render_weird_gradient(*global_back_buffer, x_offset, y_offset);
dimension := win32_get_window_dimension(window);
win32_display_buffer_window(device_context,
dimension.width,
dimension.height,
*global_back_buffer);
}
} else {
// TODO(nahua): Logging
}
} else {
// TODO(nahua): Logging
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment