diff --git a/core/bytes/util.odin b/core/bytes/util.odin new file mode 100644 index 000000000..1749230db --- /dev/null +++ b/core/bytes/util.odin @@ -0,0 +1,181 @@ +package bytes + +import "core:intrinsics" +import "core:mem" + +/* + Buffer type helpers +*/ + +need_endian_conversion :: proc($FT: typeid, $TT: typeid) -> (res: bool) { + + // true if platform endian + f: bool; + t: bool; + + when ODIN_ENDIAN == "little" { + f = intrinsics.type_is_endian_platform(FT) || intrinsics.type_is_endian_little(FT); + t = intrinsics.type_is_endian_platform(TT) || intrinsics.type_is_endian_little(TT); + + return f != t; + } else { + f = intrinsics.type_is_endian_platform(FT) || intrinsics.type_is_endian_big(FT); + t = intrinsics.type_is_endian_platform(TT) || intrinsics.type_is_endian_big(TT); + + return f != t; + } + + return; +} + +/* + Input: + count: number of elements + $TT: destination type + $FT: source type + from_buffer: buffer to convert + force_convert: cast each element separately + + Output: + res: Converted/created buffer of []TT. + backing: ^bytes.Buffer{} backing the converted data. + alloc: Buffer was freshly allocated because we couldn't convert in-place. Points to `from_buffer` if `false`. + err: True if we passed too few elements or allocation failed, etc. + + If `from_buffer` is empty, the input type $FT is ignored and `create_buffer_of_type` is called to create a fresh buffer. + + This helper will try to do as little work as possible, so if you're converting between two equally sized types, + and they have compatible endianness, the contents will simply be reinterpreted using `slice_data_cast`. + + If you want each element to be converted in this case, set `force_convert` to `true`. + + For example, converting `[]u8{0, 60}` from `[]f16` to `[]u16` will return `[15360]` when simply reinterpreted, + and `[1]` if force converted. + + Should you for example want to promote `[]f16` to `[]f32` (or truncate `[]f32` to `[]f16`), the size of these elements + being different will result in a conversion anyway, so this flag is unnecessary in cases like these. + + Example: + fmt.println("Convert []f16le (x2) to []f32 (x2)."); + b := []u8{0, 60, 0, 60}; // == []f16{1.0, 1.0} + + res, backing, had_to_allocate, err := bytes.buffer_convert_to_type(2, f32, f16le, b); + fmt.printf("res : %v\n", res); // [1.000, 1.000] + fmt.printf("backing : %v\n", backing); // &Buffer{buf = [0, 0, 128, 63, 0, 0, 128, 63], off = 0, last_read = Invalid} + fmt.printf("allocated: %v\n", had_to_allocate); // true + fmt.printf("err : %v\n", err); // false + + if had_to_allocate { defer bytes.buffer_destroy(backing); } + + fmt.println("\nConvert []f16le (x2) to []u16 (x2)."); + + res2: []u16; + res2, backing, had_to_allocate, err = bytes.buffer_convert_to_type(2, u16, f16le, b); + fmt.printf("res : %v\n", res2); // [15360, 15360] + fmt.printf("backing : %v\n", backing); // Buffer.buf points to `b` because it could be converted in-place. + fmt.printf("allocated: %v\n", had_to_allocate); // false + fmt.printf("err : %v\n", err); // false + + if had_to_allocate { defer bytes.buffer_destroy(backing); } + + fmt.println("\nConvert []f16le (x2) to []u16 (x2), force_convert=true."); + + res2, backing, had_to_allocate, err = bytes.buffer_convert_to_type(2, u16, f16le, b, true); + fmt.printf("res : %v\n", res2); // [1, 1] + fmt.printf("backing : %v\n", backing); // Buffer.buf points to `b` because it could be converted in-place. + fmt.printf("allocated: %v\n", had_to_allocate); // false + fmt.printf("err : %v\n", err); // false + + if had_to_allocate { defer bytes.buffer_destroy(backing); } +*/ +buffer_convert_to_type :: proc(count: int, $TT: typeid, $FT: typeid, from_buffer: []u8, force_convert := false) -> ( + res: []TT, backing: ^Buffer, alloc: bool, err: bool) { + + backing = new(Buffer); + + if len(from_buffer) > 0 { + /* + Check if we've been given enough input elements. + */ + from := mem.slice_data_cast([]FT, from_buffer); + if len(from) != count { + err = true; + return; + } + + /* + We can early out if the types are exactly identical. + This needs to be `when`, or res = from will fail if the types are different. + */ + when FT == TT { + res = from; + buffer_init(backing, from_buffer); + return; + } + + /* + We can do a data cast if in-size == out-size and no endian conversion is needed. + */ + convert := need_endian_conversion(FT, TT); + convert |= (size_of(TT) * count != len(from_buffer)); + convert |= force_convert; + + if !convert { + // It's just a data cast + res = mem.slice_data_cast([]TT, from_buffer); + buffer_init(backing, from_buffer); + + if len(res) != count { + err = true; + } + return; + } else { + if size_of(TT) * count == len(from_buffer) { + /* + Same size, can do an in-place Endianness conversion. + If `force_convert`, this also handles the per-element cast instead of slice_data_cast. + */ + res = mem.slice_data_cast([]TT, from_buffer); + buffer_init(backing, from_buffer); + for v, i in from { + res[i] = TT(v); + } + } else { + /* + Result is a different size, we need to allocate an output buffer. + */ + size := size_of(TT) * count; + buffer_init_allocator(backing, size, size, context.allocator); + alloc = true; + res = mem.slice_data_cast([]TT, backing.buf[:]); + if len(res) != count { + err = true; + return; + } + + for v, i in from { + res[i] = TT(v); + } + } + } + } else { + /* + The input buffer is empty, so we'll have to create a new one for []TT of length count. + */ + res, backing, err = buffer_create_of_type(count, TT); + alloc = true; + } + + return; +} + +buffer_create_of_type :: proc(count: int, $TT: typeid) -> (res: []TT, backing: ^Buffer, err: bool) { + backing = new(Buffer); + size := size_of(TT) * count; + buffer_init_allocator(backing, size, size, context.allocator); + res = mem.slice_data_cast([]TT, backing.buf[:]); + if len(res) != count { + err = true; + } + return; +} \ No newline at end of file