From 54a2b6f00e49008a1db9907213a99212e17ac5a3 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 18 Jun 2021 15:25:36 +0200 Subject: [PATCH 1/2] Add `bytes.buffer_create_of_type` and `bytes.buffer_convert_to_type`. Convenience functions to reinterpret or cast one buffer to another type, or create a buffer of a specific type. Example: ```odin 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); } ``` --- core/bytes/util.odin | 181 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 core/bytes/util.odin diff --git a/core/bytes/util.odin b/core/bytes/util.odin new file mode 100644 index 000000000..cb2216ec0 --- /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 len(from_buffer) == 0, the input type $FT is ingored 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 just want to upconvert `f16` to `f32` (or truncate `f32` to `f16`), for example, 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 From 8a4b9ddaa37b0ee8c7213bb91bf507835448ba09 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 18 Jun 2021 15:42:04 +0200 Subject: [PATCH 2/2] Fix comment. --- core/bytes/util.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/bytes/util.odin b/core/bytes/util.odin index cb2216ec0..1749230db 100644 --- a/core/bytes/util.odin +++ b/core/bytes/util.odin @@ -42,7 +42,7 @@ need_endian_conversion :: proc($FT: typeid, $TT: typeid) -> (res: bool) { 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 len(from_buffer) == 0, the input type $FT is ingored and create_buffer_of_type is called to create a fresh buffer. + 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`. @@ -52,7 +52,7 @@ need_endian_conversion :: proc($FT: typeid, $TT: typeid) -> (res: bool) { For example, converting `[]u8{0, 60}` from `[]f16` to `[]u16` will return `[15360]` when simply reinterpreted, and `[1]` if force converted. - Should you just want to upconvert `f16` to `f32` (or truncate `f32` to `f16`), for example, the size of these elements + 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: