diff --git a/core/atomic.odin b/core/atomic.odin new file mode 100644 index 000000000..e5c576ab0 --- /dev/null +++ b/core/atomic.odin @@ -0,0 +1,101 @@ +// TODO(bill): Use assembly instead here to implement atomics +// Inline vs external file? + +#import win32 "sys/windows.odin" when ODIN_OS == "windows"; +_ := compile_assert(ODIN_ARCH == "amd64"); // TODO(bill): x86 version + + +yield_thread :: proc() { win32._mm_pause(); } +mfence :: proc() { win32.ReadWriteBarrier(); } +sfence :: proc() { win32.WriteBarrier(); } +lfence :: proc() { win32.ReadBarrier(); } + + +load32 :: proc(a: ^i32) -> i32 { + return a^; +} +store32 :: proc(a: ^i32, value: i32) { + a^ = value; +} +compare_exchange32 :: proc(a: ^i32, expected, desired: i32) -> i32 { + return win32.InterlockedCompareExchange(a, desired, expected); +} +exchanged32 :: proc(a: ^i32, desired: i32) -> i32 { + return win32.InterlockedExchange(a, desired); +} +fetch_add32 :: proc(a: ^i32, operand: i32) -> i32 { + return win32.InterlockedExchangeAdd(a, operand); + +} +fetch_and32 :: proc(a: ^i32, operand: i32) -> i32 { + return win32.InterlockedAnd(a, operand); + +} +fetch_or32 :: proc(a: ^i32, operand: i32) -> i32 { + return win32.InterlockedOr(a, operand); +} +spin_lock32 :: proc(a: ^i32, time_out: int) -> bool { // NOTE(bill): time_out = -1 as default + old_value := compare_exchange32(a, 1, 0); + counter := 0; + for old_value != 0 && (time_out < 0 || counter < time_out) { + counter++; + yield_thread(); + old_value = compare_exchange32(a, 1, 0); + mfence(); + } + return old_value == 0; +} +spin_unlock32 :: proc(a: ^i32) { + store32(a, 0); + mfence(); +} +try_acquire_lock32 :: proc(a: ^i32) -> bool { + yield_thread(); + old_value := compare_exchange32(a, 1, 0); + mfence(); + return old_value == 0; +} + + +load64 :: proc(a: ^i64) -> i64 { + return a^; +} +store64 :: proc(a: ^i64, value: i64) { + a^ = value; +} +compare_exchange64 :: proc(a: ^i64, expected, desired: i64) -> i64 { + return win32.InterlockedCompareExchange64(a, desired, expected); +} +exchanged64 :: proc(a: ^i64, desired: i64) -> i64 { + return win32.InterlockedExchange64(a, desired); +} +fetch_add64 :: proc(a: ^i64, operand: i64) -> i64 { + return win32.InterlockedExchangeAdd64(a, operand); +} +fetch_and64 :: proc(a: ^i64, operand: i64) -> i64 { + return win32.InterlockedAnd64(a, operand); +} +fetch_or64 :: proc(a: ^i64, operand: i64) -> i64 { + return win32.InterlockedOr64(a, operand); +} +spin_lock64 :: proc(a: ^i64, time_out: int) -> bool { // NOTE(bill): time_out = -1 as default + old_value := compare_exchange64(a, 1, 0); + counter := 0; + for old_value != 0 && (time_out < 0 || counter < time_out) { + counter++; + yield_thread(); + old_value = compare_exchange64(a, 1, 0); + mfence(); + } + return old_value == 0; +} +spin_unlock64 :: proc(a: ^i64) { + store64(a, 0); + mfence(); +} +try_acquire_lock64 :: proc(a: ^i64) -> bool { + yield_thread(); + old_value := compare_exchange64(a, 1, 0); + mfence(); + return old_value == 0; +} diff --git a/core/sync.odin b/core/sync.odin new file mode 100644 index 000000000..eba143104 --- /dev/null +++ b/core/sync.odin @@ -0,0 +1,97 @@ +#import win32 "sys/windows.odin" when ODIN_OS == "windows"; +#import "atomic.odin"; + +Semaphore :: struct { + handle: win32.HANDLE; +} + +Mutex :: struct { + semaphore: Semaphore; + counter: i32; + owner: i32; + recursion: i32; +} + + +current_thread_id :: proc() -> i32 { + return win32.GetCurrentThreadId() as i32; +} + +semaphore_init :: proc(s: ^Semaphore) { + s.handle = win32.CreateSemaphoreA(nil, 0, 1<<31-1, nil); +} + +semaphore_destroy :: proc(s: ^Semaphore) { + win32.CloseHandle(s.handle); +} + +semaphore_post :: proc(s: ^Semaphore, count: int) { + win32.ReleaseSemaphore(s.handle, count as i32, nil); +} + +semaphore_release :: proc(s: ^Semaphore) #inline { semaphore_post(s, 1); } + +semaphore_wait :: proc(s: ^Semaphore) { + win32.WaitForSingleObject(s.handle, win32.INFINITE); +} + +mutex_make :: proc() -> Mutex { + m: Mutex + mutex_init(^m) + return m +} + +mutex_init :: proc(m: ^Mutex) { + atomic.store32(^m.counter, 0); + atomic.store32(^m.owner, current_thread_id()); + semaphore_init(^m.semaphore); + m.recursion = 0; +} +mutex_destroy :: proc(m: ^Mutex) { + semaphore_destroy(^m.semaphore); +} +mutex_lock :: proc(m: ^Mutex) { + thread_id := current_thread_id(); + if atomic.fetch_add32(^m.counter, 1) > 0 { + if thread_id != atomic.load32(^m.owner) { + semaphore_wait(^m.semaphore); + } + } + atomic.store32(^m.owner, thread_id); + m.recursion++; +} +mutex_try_lock :: proc(m: ^Mutex) -> bool { + thread_id := current_thread_id(); + if atomic.load32(^m.owner) == thread_id { + atomic.fetch_add32(^m.counter, 1); + } else { + expected: i32 = 0; + if atomic.load32(^m.counter) != 0 { + return false; + } + if atomic.compare_exchange32(^m.counter, expected, 1) == 0 { + return false; + } + atomic.store32(^m.owner, thread_id); + } + m.recursion++; + return true; +} +mutex_unlock :: proc(m: ^Mutex) { + recursion: i32; + thread_id := current_thread_id(); + assert(thread_id == atomic.load32(^m.owner)); + + m.recursion--; + recursion = m.recursion; + if recursion == 0 { + atomic.store32(^m.owner, thread_id); + } + + if atomic.fetch_add32(^m.counter, -1) > 1 { + if recursion == 0 { + semaphore_release(^m.semaphore); + } + } +} + diff --git a/core/sys/windows.odin b/core/sys/windows.odin new file mode 100644 index 000000000..df9f7f268 --- /dev/null +++ b/core/sys/windows.odin @@ -0,0 +1,525 @@ +#foreign_system_library "user32" when ODIN_OS == "windows"; +#foreign_system_library "gdi32" when ODIN_OS == "windows"; + +HANDLE :: type rawptr; +HWND :: type HANDLE; +HDC :: type HANDLE; +HINSTANCE :: type HANDLE; +HICON :: type HANDLE; +HCURSOR :: type HANDLE; +HMENU :: type HANDLE; +HBRUSH :: type HANDLE; +HGDIOBJ :: type HANDLE; +HMODULE :: type HANDLE; +WPARAM :: type uint; +LPARAM :: type int; +LRESULT :: type int; +ATOM :: type i16; +BOOL :: type i32; +WNDPROC :: type proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT; + +INVALID_HANDLE_VALUE :: (-1 as int) as HANDLE; + +CS_VREDRAW :: 0x0001; +CS_HREDRAW :: 0x0002; +CS_OWNDC :: 0x0020; +CW_USEDEFAULT :: -0x80000000; + +WS_OVERLAPPED :: 0; +WS_MAXIMIZEBOX :: 0x00010000; +WS_MINIMIZEBOX :: 0x00020000; +WS_THICKFRAME :: 0x00040000; +WS_SYSMENU :: 0x00080000; +WS_CAPTION :: 0x00C00000; +WS_VISIBLE :: 0x10000000; +WS_OVERLAPPEDWINDOW :: WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX; + +WM_DESTROY :: 0x0002; +WM_CLOSE :: 0x0010; +WM_QUIT :: 0x0012; +WM_KEYDOWN :: 0x0100; +WM_KEYUP :: 0x0101; + +PM_REMOVE :: 1; + +COLOR_BACKGROUND :: 1 as HBRUSH; +BLACK_BRUSH :: 4; + +SM_CXSCREEN :: 0; +SM_CYSCREEN :: 1; + +SW_SHOW :: 5; + +POINT :: struct #ordered { + x, y: i32; +} + + +WNDCLASSEXA :: struct #ordered { + size, style: u32; + wnd_proc: WNDPROC; + cls_extra, wnd_extra: i32; + instance: HINSTANCE; + icon: HICON; + cursor: HCURSOR; + background: HBRUSH; + menu_name, class_name: ^u8; + sm: HICON; +} + +MSG :: struct #ordered { + hwnd: HWND; + message: u32; + wparam: WPARAM; + lparam: LPARAM; + time: u32; + pt: POINT; +} + +RECT :: struct #ordered { + left: i32; + top: i32; + right: i32; + bottom: i32; +} + +FILETIME :: struct #ordered { + low_date_time, high_date_time: u32; +} + +BY_HANDLE_FILE_INFORMATION :: struct #ordered { + file_attributes: u32; + creation_time, + last_access_time, + last_write_time: FILETIME; + volume_serial_number, + file_size_high, + file_size_low, + number_of_links, + file_index_high, + file_index_low: u32; +} + +WIN32_FILE_ATTRIBUTE_DATA :: struct #ordered { + file_attributes: u32; + creation_time, + last_access_time, + last_write_time: FILETIME; + file_size_high, + file_size_low: u32; +} + +GET_FILEEX_INFO_LEVELS :: type i32; +GetFileExInfoStandard :: 0 as GET_FILEEX_INFO_LEVELS; +GetFileExMaxInfoLevel :: 1 as GET_FILEEX_INFO_LEVELS; + +GetLastError :: proc() -> i32 #foreign #dll_import +ExitProcess :: proc(exit_code: u32) #foreign #dll_import +GetDesktopWindow :: proc() -> HWND #foreign #dll_import +GetCursorPos :: proc(p: ^POINT) -> i32 #foreign #dll_import +ScreenToClient :: proc(h: HWND, p: ^POINT) -> i32 #foreign #dll_import +GetModuleHandleA :: proc(module_name: ^u8) -> HINSTANCE #foreign #dll_import +GetStockObject :: proc(fn_object: i32) -> HGDIOBJ #foreign #dll_import +PostQuitMessage :: proc(exit_code: i32) #foreign #dll_import +SetWindowTextA :: proc(hwnd: HWND, c_string: ^u8) -> BOOL #foreign #dll_import + +QueryPerformanceFrequency :: proc(result: ^i64) -> i32 #foreign #dll_import +QueryPerformanceCounter :: proc(result: ^i64) -> i32 #foreign #dll_import + +Sleep :: proc(ms: i32) -> i32 #foreign #dll_import + +OutputDebugStringA :: proc(c_str: ^u8) #foreign #dll_import + + +RegisterClassExA :: proc(wc: ^WNDCLASSEXA) -> ATOM #foreign #dll_import +CreateWindowExA :: proc(ex_style: u32, + class_name, title: ^u8, + style: u32, + x, y, w, h: i32, + parent: HWND, menu: HMENU, instance: HINSTANCE, + param: rawptr) -> HWND #foreign #dll_import + +ShowWindow :: proc(hwnd: HWND, cmd_show: i32) -> BOOL #foreign #dll_import +TranslateMessage :: proc(msg: ^MSG) -> BOOL #foreign #dll_import +DispatchMessageA :: proc(msg: ^MSG) -> LRESULT #foreign #dll_import +UpdateWindow :: proc(hwnd: HWND) -> BOOL #foreign #dll_import +PeekMessageA :: proc(msg: ^MSG, hwnd: HWND, + msg_filter_min, msg_filter_max, remove_msg: u32) -> BOOL #foreign #dll_import + +DefWindowProcA :: proc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT #foreign #dll_import + +AdjustWindowRect :: proc(rect: ^RECT, style: u32, menu: BOOL) -> BOOL #foreign #dll_import +GetActiveWindow :: proc() -> HWND #foreign #dll_import + + +GetQueryPerformanceFrequency :: proc() -> i64 { + r: i64; + QueryPerformanceFrequency(^r); + return r; +} + +GetCommandLineA :: proc() -> ^u8 #foreign #dll_import +GetSystemMetrics :: proc(index: i32) -> i32 #foreign #dll_import +GetCurrentThreadId :: proc() -> u32 #foreign #dll_import + +// File Stuff + +CloseHandle :: proc(h: HANDLE) -> i32 #foreign #dll_import +GetStdHandle :: proc(h: i32) -> HANDLE #foreign #dll_import +CreateFileA :: proc(filename: ^u8, desired_access, share_mode: u32, + security: rawptr, + creation, flags_and_attribs: u32, template_file: HANDLE) -> HANDLE #foreign #dll_import +ReadFile :: proc(h: HANDLE, buf: rawptr, to_read: u32, bytes_read: ^i32, overlapped: rawptr) -> BOOL #foreign #dll_import +WriteFile :: proc(h: HANDLE, buf: rawptr, len: i32, written_result: ^i32, overlapped: rawptr) -> i32 #foreign #dll_import + +GetFileSizeEx :: proc(file_handle: HANDLE, file_size: ^i64) -> BOOL #foreign #dll_import +GetFileAttributesExA :: proc(filename: ^u8, info_level_id: GET_FILEEX_INFO_LEVELS, file_info: rawptr) -> BOOL #foreign #dll_import +GetFileInformationByHandle :: proc(file_handle: HANDLE, file_info: ^BY_HANDLE_FILE_INFORMATION) -> BOOL #foreign #dll_import + +FILE_SHARE_READ :: 0x00000001; +FILE_SHARE_WRITE :: 0x00000002; +FILE_SHARE_DELETE :: 0x00000004; +FILE_GENERIC_ALL :: 0x10000000; +FILE_GENERIC_EXECUTE :: 0x20000000; +FILE_GENERIC_WRITE :: 0x40000000; +FILE_GENERIC_READ :: 0x80000000; + +STD_INPUT_HANDLE :: -10; +STD_OUTPUT_HANDLE :: -11; +STD_ERROR_HANDLE :: -12; + +CREATE_NEW :: 1; +CREATE_ALWAYS :: 2; +OPEN_EXISTING :: 3; +OPEN_ALWAYS :: 4; +TRUNCATE_EXISTING :: 5; + + + + + +HeapAlloc :: proc(h: HANDLE, flags: u32, bytes: int) -> rawptr #foreign #dll_import +HeapReAlloc :: proc(h: HANDLE, flags: u32, memory: rawptr, bytes: int) -> rawptr #foreign #dll_import +HeapFree :: proc(h: HANDLE, flags: u32, memory: rawptr) -> BOOL #foreign #dll_import +GetProcessHeap :: proc() -> HANDLE #foreign #dll_import + + +HEAP_ZERO_MEMORY :: 0x00000008; + +// Synchronization + +SECURITY_ATTRIBUTES :: struct #ordered { + length: u32; + security_descriptor: rawptr; + inherit_handle: BOOL; +} + +INFINITE :: 0xffffffff; + +CreateSemaphoreA :: proc(attributes: ^SECURITY_ATTRIBUTES, initial_count, maximum_count: i32, name: ^byte) -> HANDLE #foreign #dll_import +ReleaseSemaphore :: proc(semaphore: HANDLE, release_count: i32, previous_count: ^i32) -> BOOL #foreign #dll_import +WaitForSingleObject :: proc(handle: HANDLE, milliseconds: u32) -> u32 #foreign #dll_import + + +InterlockedCompareExchange :: proc(dst: ^i32, exchange, comparand: i32) -> i32 #foreign +InterlockedExchange :: proc(dst: ^i32, desired: i32) -> i32 #foreign +InterlockedExchangeAdd :: proc(dst: ^i32, desired: i32) -> i32 #foreign +InterlockedAnd :: proc(dst: ^i32, desired: i32) -> i32 #foreign +InterlockedOr :: proc(dst: ^i32, desired: i32) -> i32 #foreign + +InterlockedCompareExchange64 :: proc(dst: ^i64, exchange, comparand: i64) -> i64 #foreign +InterlockedExchange64 :: proc(dst: ^i64, desired: i64) -> i64 #foreign +InterlockedExchangeAdd64 :: proc(dst: ^i64, desired: i64) -> i64 #foreign +InterlockedAnd64 :: proc(dst: ^i64, desired: i64) -> i64 #foreign +InterlockedOr64 :: proc(dst: ^i64, desired: i64) -> i64 #foreign + +_mm_pause :: proc() #foreign +ReadWriteBarrier :: proc() #foreign +WriteBarrier :: proc() #foreign +ReadBarrier :: proc() #foreign + + +// GDI + +BITMAPINFOHEADER :: struct #ordered { + size: u32; + width, height: i32; + planes, bit_count: i16; + compression: u32; + size_image: u32; + x_pels_per_meter: i32; + y_pels_per_meter: i32; + clr_used: u32; + clr_important: u32; +} +BITMAPINFO :: struct #ordered { + using header: BITMAPINFOHEADER; + colors: [1]RGBQUAD; +} + + +RGBQUAD :: struct #ordered { + blue, green, red, reserved: byte; +} + +BI_RGB :: 0; +DIB_RGB_COLORS :: 0x00; +SRCCOPY :: 0x00cc0020 as u32; + +StretchDIBits :: proc(hdc: HDC, + x_dst, y_dst, width_dst, height_dst: i32, + x_src, y_src, width_src, header_src: i32, + bits: rawptr, bits_info: ^BITMAPINFO, + usage: u32, + rop: u32) -> i32 #foreign #dll_import + + + +LoadLibraryA :: proc(c_str: ^u8) -> HMODULE #foreign +FreeLibrary :: proc(h: HMODULE) #foreign +GetProcAddress :: proc(h: HMODULE, c_str: ^u8) -> PROC #foreign + +GetClientRect :: proc(hwnd: HWND, rect: ^RECT) -> BOOL #foreign + + + +// Windows OpenGL + +PFD_TYPE_RGBA :: 0; +PFD_TYPE_COLORINDEX :: 1; +PFD_MAIN_PLANE :: 0; +PFD_OVERLAY_PLANE :: 1; +PFD_UNDERLAY_PLANE :: -1; +PFD_DOUBLEBUFFER :: 1; +PFD_STEREO :: 2; +PFD_DRAW_TO_WINDOW :: 4; +PFD_DRAW_TO_BITMAP :: 8; +PFD_SUPPORT_GDI :: 16; +PFD_SUPPORT_OPENGL :: 32; +PFD_GENERIC_FORMAT :: 64; +PFD_NEED_PALETTE :: 128; +PFD_NEED_SYSTEM_PALETTE :: 0x00000100; +PFD_SWAP_EXCHANGE :: 0x00000200; +PFD_SWAP_COPY :: 0x00000400; +PFD_SWAP_LAYER_BUFFERS :: 0x00000800; +PFD_GENERIC_ACCELERATED :: 0x00001000; +PFD_DEPTH_DONTCARE :: 0x20000000; +PFD_DOUBLEBUFFER_DONTCARE :: 0x40000000; +PFD_STEREO_DONTCARE :: 0x80000000; + +HGLRC :: type HANDLE; +PROC :: type proc(); +wglCreateContextAttribsARBType :: type proc(hdc: HDC, hshareContext: rawptr, attribList: ^i32) -> HGLRC; + + +PIXELFORMATDESCRIPTOR :: struct #ordered { + size, + version, + flags: u32; + + pixel_type, + color_bits, + red_bits, + red_shift, + green_bits, + green_shift, + blue_bits, + blue_shift, + alpha_bits, + alpha_shift, + accum_bits, + accum_red_bits, + accum_green_bits, + accum_blue_bits, + accum_alpha_bits, + depth_bits, + stencil_bits, + aux_buffers, + layer_type, + reserved: byte; + + layer_mask, + visible_mask, + damage_mask: u32; +} + +GetDC :: proc(h: HANDLE) -> HDC #foreign +SetPixelFormat :: proc(hdc: HDC, pixel_format: i32, pfd: ^PIXELFORMATDESCRIPTOR ) -> BOOL #foreign #dll_import +ChoosePixelFormat :: proc(hdc: HDC, pfd: ^PIXELFORMATDESCRIPTOR) -> i32 #foreign #dll_import +SwapBuffers :: proc(hdc: HDC) -> BOOL #foreign #dll_import +ReleaseDC :: proc(wnd: HWND, hdc: HDC) -> i32 #foreign #dll_import + +WGL_CONTEXT_MAJOR_VERSION_ARB :: 0x2091; +WGL_CONTEXT_MINOR_VERSION_ARB :: 0x2092; +WGL_CONTEXT_PROFILE_MASK_ARB :: 0x9126; +WGL_CONTEXT_CORE_PROFILE_BIT_ARB :: 0x0001; +WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB :: 0x0002; + +wglCreateContext :: proc(hdc: HDC) -> HGLRC #foreign #dll_import +wglMakeCurrent :: proc(hdc: HDC, hglrc: HGLRC) -> BOOL #foreign #dll_import +wglGetProcAddress :: proc(c_str: ^u8) -> PROC #foreign #dll_import +wglDeleteContext :: proc(hglrc: HGLRC) -> BOOL #foreign #dll_import + + + +GetKeyState :: proc(v_key: i32) -> i16 #foreign #dll_import +GetAsyncKeyState :: proc(v_key: i32) -> i16 #foreign #dll_import + +is_key_down :: proc(key: Key_Code) -> bool { + return GetAsyncKeyState(key as i32) < 0; +} + +Key_Code :: enum i32 { + LBUTTON = 0x01, + RBUTTON = 0x02, + CANCEL = 0x03, + MBUTTON = 0x04, + + BACK = 0x08, + TAB = 0x09, + + CLEAR = 0x0C, + RETURN = 0x0D, + + SHIFT = 0x10, + CONTROL = 0x11, + MENU = 0x12, + PAUSE = 0x13, + CAPITAL = 0x14, + + KANA = 0x15, + HANGEUL = 0x15, + HANGUL = 0x15, + JUNJA = 0x17, + FINAL = 0x18, + HANJA = 0x19, + KANJI = 0x19, + + ESCAPE = 0x1B, + + CONVERT = 0x1C, + NONCONVERT = 0x1D, + ACCEPT = 0x1E, + MODECHANGE = 0x1F, + + SPACE = 0x20, + PRIOR = 0x21, + NEXT = 0x22, + END = 0x23, + HOME = 0x24, + LEFT = 0x25, + UP = 0x26, + RIGHT = 0x27, + DOWN = 0x28, + SELECT = 0x29, + PRINT = 0x2A, + EXECUTE = 0x2B, + SNAPSHOT = 0x2C, + INSERT = 0x2D, + DELETE = 0x2E, + HELP = 0x2F, + + NUM0 = '0', + NUM1 = '1', + NUM2 = '2', + NUM3 = '3', + NUM4 = '4', + NUM5 = '5', + NUM6 = '6', + NUM7 = '7', + NUM8 = '8', + NUM9 = '9', + + A = 'A', + B = 'B', + C = 'C', + D = 'D', + E = 'E', + F = 'F', + G = 'G', + H = 'H', + I = 'I', + J = 'J', + K = 'K', + L = 'L', + M = 'M', + N = 'N', + O = 'O', + P = 'P', + Q = 'Q', + R = 'R', + S = 'S', + T = 'T', + U = 'U', + V = 'V', + W = 'W', + X = 'X', + Y = 'Y', + Z = 'Z', + + LWIN = 0x5B, + RWIN = 0x5C, + APPS = 0x5D, + + NUMPAD0 = 0x60, + NUMPAD1 = 0x61, + NUMPAD2 = 0x62, + NUMPAD3 = 0x63, + NUMPAD4 = 0x64, + NUMPAD5 = 0x65, + NUMPAD6 = 0x66, + NUMPAD7 = 0x67, + NUMPAD8 = 0x68, + NUMPAD9 = 0x69, + MULTIPLY = 0x6A, + ADD = 0x6B, + SEPARATOR = 0x6C, + SUBTRACT = 0x6D, + DECIMAL = 0x6E, + DIVIDE = 0x6F, + F1 = 0x70, + F2 = 0x71, + F3 = 0x72, + F4 = 0x73, + F5 = 0x74, + F6 = 0x75, + F7 = 0x76, + F8 = 0x77, + F9 = 0x78, + F10 = 0x79, + F11 = 0x7A, + F12 = 0x7B, + F13 = 0x7C, + F14 = 0x7D, + F15 = 0x7E, + F16 = 0x7F, + F17 = 0x80, + F18 = 0x81, + F19 = 0x82, + F20 = 0x83, + F21 = 0x84, + F22 = 0x85, + F23 = 0x86, + F24 = 0x87, + + NUMLOCK = 0x90, + SCROLL = 0x91, + + LSHIFT = 0xA0, + RSHIFT = 0xA1, + LCONTROL = 0xA2, + RCONTROL = 0xA3, + LMENU = 0xA4, + RMENU = 0xA5, + PROCESSKEY = 0xE5, + ATTN = 0xF6, + CRSEL = 0xF7, + EXSEL = 0xF8, + EREOF = 0xF9, + PLAY = 0xFA, + ZOOM = 0xFB, + NONAME = 0xFC, + PA1 = 0xFD, + OEM_CLEAR = 0xFE, +} +