Browse Source

Merge branch 'master' of https://github.com/odin-lang/Odin

gingerBill 3 years ago
parent
commit
b0904d6598

+ 56 - 1
core/compress/common.odin

@@ -294,6 +294,24 @@ peek_data_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid
 	}
 }
 
+@(optimization_mode="speed")
+peek_data_at_offset_from_memory :: #force_inline proc(z: ^Context_Memory_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
+	size :: size_of(T)
+
+	#no_bounds_check {
+		if len(z.input_data) >= size + offset {
+			buf := z.input_data[offset:][:size]
+			return (^T)(&buf[0])^, .None
+		}
+	}
+
+	if len(z.input_data) == 0 {
+		return T{}, .EOF
+	} else {
+		return T{}, .Short_Buffer
+	}
+}
+
 @(optimization_mode="speed")
 peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid) -> (res: T, err: io.Error) {
 	size :: size_of(T)
@@ -321,7 +339,44 @@ peek_data_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid
 	return res, .None
 }
 
-peek_data :: proc{peek_data_from_memory, peek_data_from_stream}
+@(optimization_mode="speed")
+peek_data_at_offset_from_stream :: #force_inline proc(z: ^Context_Stream_Input, $T: typeid, #any_int offset: int) -> (res: T, err: io.Error) {
+	size :: size_of(T)
+
+	// Get current position to return to.
+	cur_pos, e1 := z.input->impl_seek(0, .Current)
+	if e1 != .None {
+		return T{}, e1
+	}
+
+	// Seek to offset.
+	pos, e2 := z.input->impl_seek(offset, .Start)
+	if e2 != .None {
+		return T{}, e2
+	}
+
+	r, e3 := io.to_reader_at(z.input)
+	if !e3 {
+		return T{}, .Empty
+	}
+	when size <= 128 {
+		b: [size]u8
+	} else {
+		b := make([]u8, size, context.temp_allocator)
+	}
+	_, e4 := io.read_at(r, b[:], pos)
+	if e4 != .None {
+		return T{}, .Empty
+	}
+
+	// Return read head to original position.
+	z.input->impl_seek(cur_pos, .Start)
+
+	res = (^T)(&b[0])^
+	return res, .None
+}
+
+peek_data :: proc{peek_data_from_memory, peek_data_from_stream, peek_data_at_offset_from_memory, peek_data_at_offset_from_stream}
 
 
 

+ 19 - 0
core/container/queue/queue.odin

@@ -73,11 +73,18 @@ get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T {
 front :: proc(q: ^$Q/Queue($T)) -> T {
 	return q.data[q.offset]
 }
+front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
+	return &q.data[q.offset]
+}
 
 back :: proc(q: ^$Q/Queue($T)) -> T {
 	idx := (q.offset+uint(q.len))%builtin.len(q.data)
 	return q.data[idx]
 }
+back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T {
+	idx := (q.offset+uint(q.len))%builtin.len(q.data)
+	return &q.data[idx]
+}
 
 set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) {
 	runtime.bounds_check_error_loc(loc, i, builtin.len(q.data))
@@ -92,6 +99,18 @@ get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^
 	return &q.data[idx]
 }
 
+peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
+	runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data))
+	idx := q.offset%builtin.len(q.data)
+	return &q.data[idx]
+}
+
+peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T {
+	runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data))
+	idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data)
+	return &q.data[idx]
+}
+
 // Push an element to the back of the queue
 push_back :: proc(q: ^$Q/Queue($T), elem: T) -> bool {
 	if space(q^) == 0 {

+ 1 - 0
core/encoding/json/marshal.odin

@@ -85,6 +85,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
 		case i16:     u = u128(i)
 		case i32:     u = u128(i)
 		case i64:     u = u128(i)
+		case i128:    u = u128(i)
 		case int:     u = u128(i)
 		case u8:      u = u128(i)
 		case u16:     u = u128(i)

+ 60 - 2
core/image/common.odin

@@ -46,7 +46,7 @@ Image :: struct {
 	height:        int,
 	channels:      int,
 	depth:         int, // Channel depth in bits, typically 8 or 16
-	pixels:        bytes.Buffer,
+	pixels:        bytes.Buffer `fmt:"-"`,
 	/*
 		Some image loaders/writers can return/take an optional background color.
 		For convenience, we return them as u16 so we don't need to switch on the type
@@ -61,6 +61,7 @@ Image_Metadata :: union #shared_nil {
 	^Netpbm_Info,
 	^PNG_Info,
 	^QOI_Info,
+	^TGA_Info,
 }
 
 
@@ -168,6 +169,7 @@ Error :: union #shared_nil {
 
 General_Image_Error :: enum {
 	None = 0,
+	Unsupported_Option,
 	// File I/O
 	Unable_To_Read_File,
 	Unable_To_Write_File,
@@ -376,10 +378,20 @@ QOI_Info :: struct {
 	header: QOI_Header,
 }
 
+TGA_Data_Type :: enum u8  {
+	No_Image_Data             = 0,
+	Uncompressed_Color_Mapped = 1,
+	Uncompressed_RGB          = 2,
+	Uncompressed_Black_White  = 3,
+	Compressed_Color_Mapped   = 9,
+	Compressed_RGB            = 10,
+	Compressed_Black_White    = 11,
+}
+
 TGA_Header :: struct #packed {
 	id_length:        u8,
 	color_map_type:   u8,
-	data_type_code:   u8,
+	data_type_code:   TGA_Data_Type,
 	color_map_origin: u16le,
 	color_map_length: u16le,
 	color_map_depth:  u8,
@@ -390,6 +402,52 @@ TGA_Header :: struct #packed {
 }
 #assert(size_of(TGA_Header) == 18)
 
+New_TGA_Signature :: "TRUEVISION-XFILE.\x00"
+
+TGA_Footer :: struct #packed {
+	extension_area_offset:      u32le,
+	developer_directory_offset: u32le,
+	signature:                  [18]u8 `fmt:"s,0"`, // Should match signature if New TGA.
+}
+#assert(size_of(TGA_Footer) == 26)
+
+TGA_Extension :: struct #packed {
+	extension_size:          u16le,               // Size of this struct. If not 495 bytes it means it's an unsupported version.
+	author_name:             [41]u8  `fmt:"s,0"`, // Author name, ASCII. Zero-terminated
+	author_comments:         [324]u8 `fmt:"s,0"`, // Author comments, formatted as 4 lines of 80 character lines, each zero terminated.
+	datetime:                struct {month, day, year, hour, minute, second: u16le},
+	job_name:                [41]u8  `fmt:"s,0"`, // Author name, ASCII. Zero-terminated
+	job_time:                struct {hour, minute, second: u16le},
+	software_id:             [41]u8  `fmt:"s,0"`, // Software ID name, ASCII. Zero-terminated
+	software_version: struct #packed {
+		number: u16le, // Version number * 100
+		letter: u8 `fmt:"r"`,   // " " if not used
+	},
+	key_color:               [4]u8,    // ARGB key color used at time of production
+	aspect_ratio:            [2]u16le, // Numerator / Denominator
+	gamma:                   [2]u16le, // Numerator / Denominator, range should be 0.0..10.0
+	color_correction_offset: u32le,    // 0 if no color correction information
+	postage_stamp_offset:    u32le,    // 0 if no thumbnail
+	scanline_offset:         u32le,    // 0 if no scanline table
+	attributes:              TGA_Alpha_Kind,
+}
+#assert(size_of(TGA_Extension) == 495)
+
+TGA_Alpha_Kind :: enum u8 {
+	None,
+	Undefined_Ignore,
+	Undefined_Retain,
+	Useful,
+	Premultiplied,
+}
+
+TGA_Info :: struct {
+	header:    TGA_Header,
+	image_id:  string,
+	footer:    Maybe(TGA_Footer),
+	extension: Maybe(TGA_Extension),
+}
+
 // Function to help with image buffer calculations
 compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
 	size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height

+ 341 - 2
core/image/tga/tga.odin

@@ -4,6 +4,7 @@
 
 	List of contributors:
 		Jeroen van Rijn: Initial implementation.
+		Benoit Jacquier: tga loader
 */
 
 
@@ -14,11 +15,16 @@ import "core:mem"
 import "core:image"
 import "core:bytes"
 import "core:os"
+import "core:compress"
+import "core:strings"
+
+// TODO: alpha_premultiply support
 
 Error   :: image.Error
 Image   :: image.Image
 Options :: image.Options
 
+GA_Pixel   :: image.GA_Pixel
 RGB_Pixel  :: image.RGB_Pixel
 RGBA_Pixel :: image.RGBA_Pixel
 
@@ -57,7 +63,7 @@ save_to_memory  :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
 	}
 
 	header := image.TGA_Header{
-		data_type_code   = 0x02, // Color, uncompressed.
+		data_type_code   = .Uncompressed_RGB,
 		dimensions       = {u16le(img.width), u16le(img.height)},
 		bits_per_pixel   = u8(img.depth * img.channels),
 		image_descriptor = 1 << 5, // Origin is top left.
@@ -98,4 +104,337 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato
 	return nil if write_ok else .Unable_To_Write_File
 }
 
-save :: proc{save_to_memory, save_to_file}
+save :: proc{save_to_memory, save_to_file}
+
+load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	context.allocator = allocator
+	options := options
+
+	if .alpha_premultiply in options {
+		return nil, .Unsupported_Option
+	}
+
+	if .info in options {
+		options |= {.return_metadata, .do_not_decompress_image}
+		options -= {.info}
+	}
+
+	if .return_header in options && .return_metadata in options {
+		options -= {.return_header}
+	}
+
+	// First check for a footer.
+	filesize := compress.input_size(ctx) or_return
+
+	footer: image.TGA_Footer
+	have_valid_footer := false
+
+	extension: image.TGA_Extension
+	have_valid_extension := false
+
+	if filesize >= size_of(image.TGA_Header) + size_of(image.TGA_Footer) {
+		if f, f_err := compress.peek_data(ctx, image.TGA_Footer, filesize - i64(size_of(image.TGA_Footer))); f_err == .None {
+			if string(f.signature[:]) == image.New_TGA_Signature {
+				have_valid_footer = true
+				footer = f
+
+				if i64(footer.extension_area_offset) + i64(size_of(image.TGA_Extension)) < filesize {
+					if e, e_err := compress.peek_data(ctx, image.TGA_Extension, footer.extension_area_offset); e_err == .None {
+						if e.extension_size == size_of(image.TGA_Extension) {
+							have_valid_extension = true
+							extension = e
+						}
+					}
+				}
+			}
+		}
+	}
+
+	header := image.read_data(ctx, image.TGA_Header) or_return
+
+	// Header checks
+	rle_encoding  := false
+	color_mapped  := false
+	black_white   := false
+	src_channels  := 0
+	dest_depth    := header.bits_per_pixel
+	dest_channels := 0
+
+	#partial switch header.data_type_code {
+	// Supported formats: RGB(A), RGB(A) RLE
+	case .Compressed_RGB:
+		rle_encoding = true
+	case .Uncompressed_RGB:
+		// Intentionally blank
+	case .Uncompressed_Black_White:
+		black_white  = true
+		dest_depth   = 24
+	case .Uncompressed_Color_Mapped:
+		color_mapped = true
+	case .Compressed_Color_Mapped:
+		color_mapped = true
+		rle_encoding = true
+	case .Compressed_Black_White:
+		black_white  = true
+		rle_encoding = true
+		dest_depth   = 24
+
+	case:
+		return nil, .Unsupported_Format
+	}
+
+	if color_mapped {
+		if header.color_map_type != 1 {
+			return nil, .Unsupported_Format
+		}
+		dest_depth = header.color_map_depth
+
+		// Expect LUT entry index to be 8 bits
+		if header.bits_per_pixel != 8 || header.color_map_origin != 0 || header.color_map_length > 256 {
+			return nil, .Unsupported_Format
+		}
+	}
+
+	switch dest_depth {
+	case 15: // B5G5R5
+		src_channels  = 2
+		dest_channels = 3
+		if color_mapped {
+			src_channels = 1
+		}
+	case 16: // B5G5R5A1
+		src_channels  = 2
+		dest_channels = 3 // Alpha bit is dodgy in TGA, so we ignore it.
+		if color_mapped {
+			src_channels = 1
+		}
+	case 24: // RGB8
+		src_channels  = 1 if (color_mapped || black_white) else 3
+		dest_channels = 3
+	case 32: // RGBA8
+		src_channels  = 4 if !color_mapped else 1
+		dest_channels = 4
+
+	case:
+		return nil, .Unsupported_Format
+	}
+
+	if header.image_descriptor & IMAGE_DESCRIPTOR_INTERLEAVING_MASK != 0 {
+		return nil, .Unsupported_Format
+	}
+
+	if int(header.dimensions[0]) * int(header.dimensions[1]) > image.MAX_DIMENSIONS {
+		return nil, .Image_Dimensions_Too_Large
+	}
+
+	if img == nil {
+		img = new(Image)
+	}
+
+	defer if err != nil {
+		destroy(img)
+	}
+
+	img.which = .TGA
+	img.channels = 4 if .alpha_add_if_missing  in options else dest_channels
+	img.channels = 3 if .alpha_drop_if_present in options else img.channels
+
+	img.depth  = 8
+	img.width  = int(header.dimensions[0])
+	img.height = int(header.dimensions[1])
+
+	// Read Image ID if present
+	image_id := ""
+	if _id, e := compress.read_slice(ctx, int(header.id_length)); e != .None {
+		return img, .Corrupt
+	} else {
+		if .return_metadata in options {
+			id := strings.trim_right_null(string(_id))
+			image_id = strings.clone(id)
+		}
+	}
+
+	color_map := make([]RGBA_Pixel, header.color_map_length)
+	defer delete(color_map)
+
+	if color_mapped {
+		switch header.color_map_depth {
+		case 16:
+			for i in 0..<header.color_map_length {
+				if lut, lut_err := compress.read_data(ctx, GA_Pixel); lut_err != .None {
+					return img, .Corrupt
+				} else {
+					color_map[i].rg = lut
+					color_map[i].ba = 255
+				}
+			}
+
+		case 24:
+			for i in 0..<header.color_map_length {
+				if lut, lut_err := compress.read_data(ctx, RGB_Pixel); lut_err != .None {
+					return img, .Corrupt
+				} else {
+					color_map[i].rgb = lut
+					color_map[i].a   = 255
+				}
+			}
+
+		case 32:
+			for i in 0..<header.color_map_length {
+				if lut, lut_err := compress.read_data(ctx, RGBA_Pixel); lut_err != .None {
+					return img, .Corrupt
+				} else {
+					color_map[i] = lut
+				}
+			}
+		}
+	}
+
+	if .return_metadata in options {
+		info := new(image.TGA_Info)
+		info.header   = header
+		info.image_id = image_id
+		if have_valid_footer {
+			info.footer = footer
+		}
+		if have_valid_extension {
+			info.extension = extension
+		}
+		img.metadata = info
+	}
+
+	if .do_not_decompress_image in options {
+		return img, nil
+	}
+
+	if !resize(&img.pixels.buf, dest_channels * img.width * img.height) {
+		return img, .Unable_To_Allocate_Or_Resize
+	}
+
+	origin_is_top        := header.image_descriptor & IMAGE_DESCRIPTOR_TOP_MASK   != 0
+	origin_is_left       := header.image_descriptor & IMAGE_DESCRIPTOR_RIGHT_MASK == 0
+	rle_repetition_count := 0
+	read_pixel           := true
+	is_packet_rle        := false
+
+	pixel: RGBA_Pixel
+
+	stride := img.width * dest_channels
+	line   := 0 if origin_is_top else img.height - 1
+
+	for _ in 0..<img.height {
+		offset := line * stride + (0 if origin_is_left else (stride - dest_channels))
+		for _ in 0..<img.width {
+			// handle RLE decoding
+			if rle_encoding {
+				if rle_repetition_count == 0 {
+					rle_cmd, err := compress.read_u8(ctx)
+					if err != .None {
+						return img, .Corrupt
+					}
+					is_packet_rle = (rle_cmd >> 7) != 0
+					rle_repetition_count = 1 + int(rle_cmd & 0x7F)
+					read_pixel = true
+				} else if !is_packet_rle {
+					read_pixel = rle_repetition_count > 0
+				} else {
+					read_pixel = false
+				}
+			}
+			// Read pixel
+			if read_pixel {
+				src, src_err := compress.read_slice(ctx, src_channels)
+				if src_err != .None {
+					return img, .Corrupt
+				}
+				switch src_channels {
+				case 1:
+					// Color-mapped or Black & White
+					if black_white {
+						pixel = {src[0], src[0], src[0], 255}
+					} else if header.color_map_depth == 24 {
+						pixel = color_map[src[0]].bgra
+					} else if header.color_map_depth == 16 {
+						lut := color_map[src[0]]
+						v := u16(lut.r) | u16(lut.g) << 8
+						b := u8( v        & 31) << 3
+						g := u8((v >>  5) & 31) << 3
+						r := u8((v >> 10) & 31) << 3
+						pixel = {r, g, b, 255}
+					}
+
+				case 2:
+					v := u16(src[0]) | u16(src[1]) << 8
+					b := u8( v        & 31) << 3
+					g := u8((v >>  5) & 31) << 3
+					r := u8((v >> 10) & 31) << 3
+					pixel = {r, g, b, 255}
+
+				case 3:
+					pixel = {src[2], src[1], src[0], 255}
+				case 4:
+					pixel = {src[2], src[1], src[0], src[3]}
+				case:
+					return img, .Corrupt
+				}
+			}
+
+			// Write pixel
+			copy(img.pixels.buf[offset:], pixel[:dest_channels])
+			offset += dest_channels if origin_is_left else -dest_channels
+			rle_repetition_count -= 1
+		}
+		line += 1 if origin_is_top else -1
+	}
+	return img, nil
+}
+
+load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	ctx := &compress.Context_Memory_Input{
+		input_data = data,
+	}
+
+	img, err = load_from_context(ctx, options, allocator)
+	return img, err
+}
+
+load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
+	context.allocator = allocator
+
+	data, ok := os.read_entire_file(filename)
+	defer delete(data)
+
+	if ok {
+		return load_from_bytes(data, options)
+	} else {
+		return nil, .Unable_To_Read_File
+	}
+}
+
+load :: proc{load_from_file, load_from_bytes, load_from_context}
+
+destroy :: proc(img: ^Image) {
+	if img == nil || img.width == 0 || img.height == 0 {
+		return
+	}
+
+	bytes.buffer_destroy(&img.pixels)
+	if v, ok := img.metadata.(^image.TGA_Info); ok {
+		delete(v.image_id)
+		free(v)
+	}
+
+	// Make destroy idempotent
+	img.width  = 0
+	img.height = 0
+	free(img)
+}
+
+IMAGE_DESCRIPTOR_INTERLEAVING_MASK :: (1<<6) | (1<<7)
+IMAGE_DESCRIPTOR_RIGHT_MASK :: 1<<4
+IMAGE_DESCRIPTOR_TOP_MASK   :: 1<<5
+
+@(init, private)
+_register :: proc() {
+	image.register(.TGA, load_from_bytes, destroy)
+}

+ 151 - 0
core/sys/windows/user32.odin

@@ -190,6 +190,14 @@ foreign user32 {
 	SetWindowTextW :: proc(hWnd: HWND, lpString: LPCWSTR) -> BOOL ---
 	CallWindowProcW :: proc(lpPrevWndFunc: WNDPROC, hWnd: HWND, Msg: UINT, wParam: WPARAM, lParam: LPARAM) -> LRESULT ---
 	EnableWindow :: proc(hWnd: HWND, bEnable: BOOL) -> BOOL ---
+
+	DefRawInputProc :: proc(paRawInput: ^PRAWINPUT, nInput: INT, cbSizeHeader: UINT) -> LRESULT ---
+	GetRawInputBuffer :: proc(pRawInput: PRAWINPUT, pcbSize: PUINT, cbSizeHeader: UINT) -> UINT ---
+	GetRawInputData :: proc(hRawInput: HRAWINPUT, uiCommand: UINT, pData: LPVOID, pcbSize: PUINT, cbSizeHeader: UINT) -> UINT ---
+	GetRawInputDeviceInfoW :: proc(hDevice: HANDLE, uiCommand: UINT, pData: LPVOID, pcbSize: PUINT) -> UINT ---
+	GetRawInputDeviceList :: proc(pRawInputDeviceList: PRAWINPUTDEVICELIST, puiNumDevices: PUINT, cbSize: UINT) -> UINT ---
+	GetRegisteredRawInputDevices :: proc(pRawInputDevices: PRAWINPUTDEVICE, puiNumDevices: PUINT, cbSize: UINT) -> UINT ---
+	RegisterRawInputDevices :: proc(pRawInputDevices: PCRAWINPUTDEVICE, uiNumDevices: UINT, cbSize: UINT) -> BOOL ---
 }
 
 CreateWindowW :: #force_inline proc "stdcall" (
@@ -277,3 +285,146 @@ DPI_AWARENESS_CONTEXT_SYSTEM_AWARE         :: DPI_AWARENESS_CONTEXT(~uintptr(1))
 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE    :: DPI_AWARENESS_CONTEXT(~uintptr(2)) // -3
 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 :: DPI_AWARENESS_CONTEXT(~uintptr(3)) // -4
 DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED    :: DPI_AWARENESS_CONTEXT(~uintptr(4)) // -5
+
+RAWINPUTHEADER :: struct {
+	dwType: DWORD,
+	dwSize: DWORD,
+	hDevice: HANDLE,
+	wParam: WPARAM,
+}
+
+RAWHID :: struct {
+	dwSizeHid: DWORD,
+	dwCount: DWORD,
+	bRawData: [1]BYTE,
+}
+
+RAWMOUSE :: struct {
+	usFlags: USHORT,
+	DUMMYUNIONNAME: struct #raw_union {
+		ulButtons: ULONG,
+		DUMMYSTRUCTNAME: struct {
+			usButtonFlags: USHORT,
+			usButtonData: USHORT,
+		},
+	},
+	ulRawButtons: ULONG,
+	lLastX: LONG,
+	lLastY: LONG,
+	ulExtraInformation: ULONG,
+}
+
+RAWKEYBOARD :: struct {
+	MakeCode: USHORT,
+	Flags: USHORT,
+	Rserved: USHORT,
+	VKey: USHORT,
+	Message: UINT,
+	ExtraInformation: ULONG,
+}
+
+RAWINPUT :: struct {
+	header: RAWINPUTHEADER,
+	data: struct #raw_union {
+		mouse: RAWMOUSE,
+		keyboard: RAWKEYBOARD,
+		hid: RAWHID,
+	},
+}
+
+PRAWINPUT :: ^RAWINPUT
+HRAWINPUT :: distinct LPARAM
+
+RAWINPUTDEVICE :: struct {
+	usUsagePage: USHORT,
+	usUsage: USHORT,
+	dwFlags: DWORD,
+	hwndTarget: HWND,
+}
+
+PRAWINPUTDEVICE :: ^RAWINPUTDEVICE
+PCRAWINPUTDEVICE :: PRAWINPUTDEVICE
+
+RAWINPUTDEVICELIST :: struct {
+	hDevice: HANDLE,
+	dwType: DWORD,
+}
+
+PRAWINPUTDEVICELIST :: ^RAWINPUTDEVICELIST
+
+RID_DEVICE_INFO_HID :: struct {
+	dwVendorId: DWORD,
+	dwProductId: DWORD,
+	dwVersionNumber: DWORD,
+	usUsagePage: USHORT,
+	usUsage: USHORT,
+}
+
+RID_DEVICE_INFO_KEYBOARD :: struct {
+	dwType: DWORD,
+	dwSubType: DWORD,
+	dwKeyboardMode: DWORD,
+	dwNumberOfFunctionKeys: DWORD,
+	dwNumberOfIndicators: DWORD,
+	dwNumberOfKeysTotal: DWORD,
+}
+
+RID_DEVICE_INFO_MOUSE :: struct {
+	dwId: DWORD,
+	dwNumberOfButtons: DWORD,
+	dwSampleRate: DWORD,
+	fHasHorizontalWheel: BOOL,
+}
+
+RID_DEVICE_INFO :: struct {
+	cbSize: DWORD,
+	dwType: DWORD,
+	DUMMYUNIONNAME: struct #raw_union {
+		mouse: RID_DEVICE_INFO_MOUSE,
+		keyboard: RID_DEVICE_INFO_KEYBOARD,
+		hid: RID_DEVICE_INFO_HID,
+	},
+}
+
+RIDEV_REMOVE :: 0x00000001
+RIDEV_EXCLUDE :: 0x00000010
+RIDEV_PAGEONLY :: 0x00000020
+RIDEV_NOLEGACY :: 0x00000030
+RIDEV_INPUTSINK :: 0x00000100
+RIDEV_CAPTUREMOUSE :: 0x00000200
+RIDEV_NOHOTKEYS :: 0x00000200
+RIDEV_APPKEYS :: 0x00000400
+RIDEV_EXINPUTSINK :: 0x00001000
+RIDEV_DEVNOTIFY :: 0x00002000
+
+RID_HEADER :: 0x10000005
+RID_INPUT :: 0x10000003
+
+RIM_TYPEMOUSE :: 0
+RIM_TYPEKEYBOARD :: 1
+RIM_TYPEHID :: 2
+
+MOUSE_MOVE_RELATIVE :: 0x00
+MOUSE_MOVE_ABSOLUTE :: 0x01
+MOUSE_VIRTUAL_DESKTOP :: 0x02
+MOUSE_ATTRIUBTTES_CHANGED :: 0x04
+MOUSE_MOVE_NOCOALESCE :: 0x08
+
+RI_MOUSE_BUTTON_1_DOWN :: 0x0001
+RI_MOUSE_LEFT_BUTTON_DOWNS :: RI_MOUSE_BUTTON_1_DOWN
+RI_MOUSE_BUTTON_1_UP :: 0x0002
+RI_MOUSE_LEFT_BUTTON_UP :: RI_MOUSE_BUTTON_1_UP
+RI_MOUSE_BUTTON_2_DOWN :: 0x0004
+RI_MOUSE_RIGHT_BUTTON_DOWN :: RI_MOUSE_BUTTON_2_DOWN
+RI_MOUSE_BUTTON_2_UP :: 0x0008
+RI_MOUSE_RIGHT_BUTTON_UP :: RI_MOUSE_BUTTON_2_UP
+RI_MOUSE_BUTTON_3_DOWN :: 0x0010
+RI_MOUSE_MIDDLE_BUTTON_DOWN :: RI_MOUSE_BUTTON_3_DOWN
+RI_MOUSE_BUTTON_3_UP :: 0x0020
+RI_MOUSE_MIDDLE_BUTTON_UP :: RI_MOUSE_BUTTON_3_UP
+RI_MOUSE_BUTTON_4_DOWN :: 0x0040
+RI_MOUSE_BUTTON_4_UP :: 0x0080
+RI_MOUSE_BUTTON_5_DOWN :: 0x0100
+RI_MOUSE_BUTTON_5_UP :: 0x0200
+RI_MOUSE_WHEEL :: 0x0400
+RI_MOUSE_HWHEEL :: 0x0800