#+build windows package main import "core:fmt" import "core:sys/windows" DEBUG_DRAW_TIMINGS :: #config(DEBUG_DRAW_TIMINGS, false) global_is_running := true window_proc :: proc "std" ( hwnd: windows.HWND, msg: u32, wparam: windows.WPARAM, lparam: windows.LPARAM, ) -> windows.LRESULT {result: windows.LRESULT switch msg { case windows.WM_DESTROY: global_is_running = false msg := cstring("WM_DESTROY\n") windows.OutputDebugStringW(([^]u16)(&msg)) case windows.WM_CLOSE: global_is_running = false msg := cstring("WM_CLOSE\n") windows.OutputDebugStringW(([^]u16)(&msg)) case: result = windows.DefWindowProcW(hwnd, msg, wparam, lparam) } return result } Backbuffer :: struct { memory: rawptr, bitmap_info: windows.BITMAPINFO, width: u32, height: u32, bps: u32, pitch: u32, } resize_backbuffer :: proc(backbuffer: ^Backbuffer, width: u32, height: u32) { if backbuffer.memory != nil { windows.VirtualFree(backbuffer.memory, 0, windows.MEM_RELEASE) } backbuffer.width = width backbuffer.height = height backbuffer.bps = 4 backbuffer.pitch = width * height backbuffer.bitmap_info.bmiHeader = { biSize = size_of(backbuffer.bitmap_info.bmiHeader), biWidth = i32(width), biHeight = -i32(height), biPlanes = 1, biBitCount = 32, biCompression = windows.BI_RGB, } backbuffer.memory = windows.VirtualAlloc( nil, uint(backbuffer.bps * backbuffer.pitch), windows.MEM_RESERVE | windows.MEM_COMMIT, windows.PAGE_READWRITE, ) } get_clock_value :: #force_inline proc() -> i64 { result: windows.LARGE_INTEGER windows.QueryPerformanceCounter(&result) return i64(result) } global_perf_count_frequency: i64 get_seconds_elapsed :: #force_inline proc(start, end: i64) -> f32 { return f32(end - start) / f32(global_perf_count_frequency) } blit_buffer_in_window :: proc(backbuffer: ^Backbuffer, dc: windows.HDC, width, height: i32) { ratio: f32 : 16.0 / 9.0 fixed_width := i32(f32(height) * ratio) offset_x := (width - fixed_width) / 2 if fixed_width != width { windows.PatBlt(dc, 0, 0, offset_x, height, windows.BLACKNESS) windows.PatBlt(dc, width - offset_x, 0, offset_x, height, windows.BLACKNESS) } windows.StretchDIBits( dc, offset_x, 0, fixed_width, height, 0, 0, i32(backbuffer.width), i32(backbuffer.height), backbuffer.memory, &backbuffer.bitmap_info, windows.DIB_RGB_COLORS, windows.SRCCOPY, ) } TIMERR_BASE :: 96 TIMERR_NOCANDO :: TIMERR_BASE + 1 TIMERR_NOERROR :: 0 input_key_translation := map[uint]AppInputKey { windows.VK_ESCAPE = .Escape, windows.VK_0 = .Num0, windows.VK_1 = .Num1, windows.VK_2 = .Num2, windows.VK_3 = .Num3, windows.VK_4 = .Num4, windows.VK_5 = .Num5, windows.VK_6 = .Num6, windows.VK_7 = .Num7, windows.VK_8 = .Num8, windows.VK_9 = .Num9, windows.VK_LEFT = .ArrowLeft, windows.VK_RIGHT = .ArrowRight, } main :: proc() { fmt.println("Hellope!") hinstance := windows.GetModuleHandleW(nil) fmt.printf("hinstance %x\n", hinstance) windows.QueryPerformanceFrequency(((^windows.LARGE_INTEGER)(&global_perf_count_frequency))) fmt.printf("perf_count_frequency: %d\n", global_perf_count_frequency) sleep_is_granular := (windows.timeBeginPeriod(1) == TIMERR_NOERROR) if sleep_is_granular { fmt.printf("-=sleep is granular=-\n") } class_name := windows.utf8_to_wstring("sandboxwindowsplatform_odin") window_class := windows.WNDCLASSW { style = windows.CS_OWNDC | windows.CS_HREDRAW | windows.CS_VREDRAW, lpfnWndProc = window_proc, hInstance = windows.HINSTANCE(hinstance), hCursor = windows.LoadCursorW(nil, ([^]u16)(windows._IDC_ARROW)), lpszClassName = class_name, } fmt.printf("window_class %v\n", window_class) if windows.RegisterClassW(&window_class) == 0 { msg := cstring("could not register class\n") windows.OutputDebugStringW(([^]u16)(&msg)) } wsize := windows.RECT { right = 1280, bottom = 720, } windows.AdjustWindowRect(&wsize, windows.WS_OVERLAPPEDWINDOW, false) fmt.printf("adjusted wndrect %v\n", wsize) window_title := windows.utf8_to_wstring("odin-lang hello winapi") window := windows.CreateWindowW( class_name, window_title, windows.WS_OVERLAPPEDWINDOW | windows.WS_VISIBLE, windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, wsize.right - wsize.left, wsize.bottom - wsize.top, nil, nil, windows.HINSTANCE(hinstance), nil, ) if window == nil { msg := cstring("could not create window\n") windows.OutputDebugStringW(([^]u16)(&msg)) return } defer windows.DestroyWindow(window) backbuffer_width :: 1280 / 2 backbuffer_height :: 720 / 2 backbuffer_index: u32 backbuffers: [2]Backbuffer resize_backbuffer(&backbuffers[0], backbuffer_width, backbuffer_height) resize_backbuffer(&backbuffers[1], backbuffer_width, backbuffer_height) defer windows.VirtualFree(backbuffers[0].memory, 0, windows.MEM_RELEASE) defer windows.VirtualFree(backbuffers[1].memory, 0, windows.MEM_RELEASE) fmt.printf("backbuffer[0]: %+v\n", backbuffers[0]) fmt.printf("backbuffer[1]: %+v\n", backbuffers[1]) game_update_hz := f32(30) target_seconds_per_frame := 1.0 / game_update_hz last_counter := get_clock_value() when DEBUG_DRAW_TIMINGS { debug_frame_timings: [256]f32 debug_render_timings: [256]f32 debug_highest_timing := f32(1) } input: [2]App_Input curr_input := &input[0] prev_input := &input[1] dc := windows.GetDC(window) defer windows.ReleaseDC(window, dc) for global_is_running { message: windows.MSG for windows.PeekMessageW(&message, window, 0, 0, windows.PM_REMOVE) { switch message.message { case windows.WM_QUIT: global_is_running = false msg := cstring("WM_QUIT\n") windows.OutputDebugStringW(([^]u16)(&msg)) case windows.WM_SYSKEYDOWN, windows.WM_SYSKEYUP, windows.WM_KEYDOWN, windows.WM_KEYUP: msg := cstring("WM_SYS/KEY\n") windows.OutputDebugStringW(([^]u16)(&msg)) key_code := uint(message.wParam) if key_code == windows.VK_ESCAPE { global_is_running = false } if translated_key, ok := input_key_translation[key_code]; ok { curr_input.keyboard[translated_key].down = ((message.lParam & (1 << 31)) == 0) } case: windows.TranslateMessage(&message) windows.DispatchMessageW(&message) } } mouse_point: windows.POINT windows.GetCursorPos(&mouse_point) windows.ScreenToClient(window, &mouse_point) curr_input.mouse_x = mouse_point.x / 2 curr_input.mouse_y = mouse_point.y / 2 curr_input.mouse_button[0].down = ((int(windows.GetKeyState(windows.VK_LBUTTON)) & (1 << 15)) != 0) curr_input.mouse_button[0].transition = (curr_input.mouse_button[0].down != prev_input.mouse_button[0].down) backbuffer := &backbuffers[backbuffer_index] backbuffer_index = (backbuffer_index + 1) % 2 screen_buffer := Bitmap { buffer = ([^]u32)(backbuffer.memory)[0:backbuffer.pitch], width = backbuffer.width, height = backbuffer.height, } when DEBUG_DRAW_TIMINGS { update_and_render_timing_start := get_clock_value() } app_update_and_render(&screen_buffer, curr_input) when DEBUG_DRAW_TIMINGS { update_and_render_timing_stop := get_clock_value() } curr_input, prev_input = prev_input, curr_input when DEBUG_DRAW_TIMINGS { pix_step := 128 / debug_highest_timing target_ms := 1000 * target_seconds_per_frame target_ms_line := int(((target_ms * (target_ms / debug_highest_timing)) * pix_step) + 0.5) for x := 0; x < 256; x += 1 { frame_height: int render_height: int { scaling := debug_frame_timings[x] / debug_highest_timing frame_height = int(((debug_frame_timings[x] * scaling) * pix_step) + 0.5) } { scaling := debug_render_timings[x] / debug_highest_timing render_height = int(((debug_render_timings[x] * scaling) * pix_step) + 0.5) } for y := 0; y < 128; y += 1 { i := ((127 - y) * int(screen_buffer.width)) + x c: u32 = 0xFF000000 if y == target_ms_line { c = 0xFFFF00FF } else if y <= render_height { if render_height > target_ms_line { c = 0xFFFF0000 } else { c = 0xFF00AA00 } } else if y <= frame_height { c = 0xFF0000AA } screen_buffer.buffer[i] = c } } } elapsed := get_seconds_elapsed(last_counter, get_clock_value()) if elapsed < target_seconds_per_frame { if sleep_is_granular { sleep_ms := u32(1000 * (target_seconds_per_frame - elapsed)) if sleep_ms > 0 { windows.Sleep(sleep_ms) } } for elapsed < target_seconds_per_frame { elapsed = get_seconds_elapsed(last_counter, get_clock_value()) } } else { fmt.printf("missed sleep\n") } client_rect: windows.RECT windows.GetClientRect(window, &client_rect) dim_width := client_rect.right - client_rect.left dim_height := client_rect.bottom - client_rect.top blit_buffer_in_window(backbuffer, dc, dim_width, dim_height) when DEBUG_DRAW_TIMINGS { ms_per_frame := 1000.0 * elapsed frame_render_ms := 1000.0 * get_seconds_elapsed(update_and_render_timing_start, update_and_render_timing_stop) debug_highest_timing = 0 for i := 0; i < 255; i += 1 { debug_frame_timings[i] = debug_frame_timings[i + 1] if debug_frame_timings[i] > debug_highest_timing { debug_highest_timing = debug_frame_timings[i] } debug_render_timings[i] = debug_render_timings[i + 1] if debug_render_timings[i] > debug_highest_timing { debug_highest_timing = debug_render_timings[i] } } debug_frame_timings[255] = ms_per_frame if ms_per_frame > debug_highest_timing { debug_highest_timing = ms_per_frame } debug_render_timings[255] = frame_render_ms if frame_render_ms > debug_highest_timing { debug_highest_timing = frame_render_ms } } last_counter = get_clock_value() } }