diff --git a/core/crypto/sha2/sha2.odin b/core/crypto/sha2/sha2.odin index 7c7b2da81..9792a4cb8 100644 --- a/core/crypto/sha2/sha2.odin +++ b/core/crypto/sha2/sha2.odin @@ -419,8 +419,10 @@ update :: proc(ctx: ^$T, data: []byte) { sha2_transf(ctx, shifted_message, block_nb) rem_len = new_len % CURR_BLOCK_SIZE - when T == Sha256_Context {copy(ctx.block[:], shifted_message[block_nb << 6:rem_len])} - else when T == Sha512_Context {copy(ctx.block[:], shifted_message[block_nb << 7:rem_len])} + if rem_len > 0 { + when T == Sha256_Context {copy(ctx.block[:], shifted_message[block_nb << 6:rem_len])} + else when T == Sha512_Context {copy(ctx.block[:], shifted_message[block_nb << 7:rem_len])} + } ctx.length = rem_len when T == Sha256_Context {ctx.tot_len += (block_nb + 1) << 6} diff --git a/core/hash/xxhash/streaming.odin b/core/hash/xxhash/streaming.odin index 68a4920a5..6f630b042 100644 --- a/core/hash/xxhash/streaming.odin +++ b/core/hash/xxhash/streaming.odin @@ -211,7 +211,9 @@ XXH3_update :: #force_inline proc( length := len(input) secret := state.custom_secret[:] if len(state.external_secret) == 0 else state.external_secret[:] - assert(len(input) > 0) + if len(input) == 0 { + return + } state.total_length += u64(length) assert(state.buffered_size <= XXH3_INTERNAL_BUFFER_SIZE) diff --git a/core/hash/xxhash/xxhash_32.odin b/core/hash/xxhash/xxhash_32.odin index e63d998dd..5bc87c2c0 100644 --- a/core/hash/xxhash/xxhash_32.odin +++ b/core/hash/xxhash/xxhash_32.odin @@ -197,6 +197,7 @@ XXH32 :: proc(input: []u8, seed := XXH32_DEFAULT_SEED) -> (digest: XXH32_hash) { */ XXH32_create_state :: proc(allocator := context.allocator) -> (res: ^XXH32_state, err: Error) { state := new(XXH32_state, allocator) + XXH32_reset_state(state) return state, .None if state != nil else .Error } @@ -258,7 +259,7 @@ XXH32_update :: proc(state: ^XXH32_state, input: []u8) -> (err: Error) { v3 := state.v3 v4 := state.v4 - for len(buf) >= 15 { + for len(buf) >= 16 { #no_bounds_check v1 = XXH32_round(v1, XXH32_read32(buf, .Unaligned)); buf = buf[4:] #no_bounds_check v2 = XXH32_round(v2, XXH32_read32(buf, .Unaligned)); buf = buf[4:] #no_bounds_check v3 = XXH32_round(v3, XXH32_read32(buf, .Unaligned)); buf = buf[4:] diff --git a/core/hash/xxhash/xxhash_64.odin b/core/hash/xxhash/xxhash_64.odin index e95842168..9280e9c59 100644 --- a/core/hash/xxhash/xxhash_64.odin +++ b/core/hash/xxhash/xxhash_64.odin @@ -163,6 +163,7 @@ XXH64 :: proc(input: []u8, seed := XXH64_DEFAULT_SEED) -> (digest: XXH64_hash) { */ XXH64_create_state :: proc(allocator := context.allocator) -> (res: ^XXH64_state, err: Error) { state := new(XXH64_state, allocator) + XXH64_reset_state(state) return state, .None if state != nil else .Error } diff --git a/core/io/io.odin b/core/io/io.odin index e9d839efb..3ad34d607 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -4,7 +4,6 @@ package io import "core:intrinsics" -import "core:runtime" import "core:unicode/utf8" // Seek whence values @@ -254,11 +253,7 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64, n_read: ^int = nil) -> (n: return 0, .Empty } - curr_offset: i64 - curr_offset, err = r->impl_seek(offset, .Current) - if err != nil { - return 0, err - } + curr_offset := r->impl_seek(offset, .Current) or_return n, err = r->impl_read(p) _, err1 := r->impl_seek(curr_offset, .Start) @@ -552,7 +547,7 @@ _copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, er } } // NOTE(bill): alloca is fine here - buf = transmute([]byte)runtime.Raw_Slice{intrinsics.alloca(size, 2*align_of(rawptr)), size} + buf = intrinsics.alloca(size, 2*align_of(rawptr))[:size] } for { nr, er := read(src, buf) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index b8bd9a065..118a7f7e6 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -746,6 +746,8 @@ dynamic_pool_reset :: proc(using pool: ^Dynamic_Pool) { free(a, block_allocator) } clear(&out_band_allocations) + + bytes_left = 0 // Make new allocations call `cycle_new_block` again. } dynamic_pool_free_all :: proc(using pool: ^Dynamic_Pool) { diff --git a/core/os/os.odin b/core/os/os.odin index e880ec21e..5e71e720e 100644 --- a/core/os/os.odin +++ b/core/os/os.odin @@ -9,6 +9,10 @@ OS :: ODIN_OS ARCH :: ODIN_ARCH ENDIAN :: ODIN_ENDIAN +SEEK_SET :: 0 +SEEK_CUR :: 1 +SEEK_END :: 2 + write_string :: proc(fd: Handle, str: string) -> (int, Errno) { return write(fd, transmute([]byte)str) } diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index af04db858..2ce755203 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,7 +1,6 @@ //+private package os2 -import "core:mem" import win32 "core:sys/windows" _lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { @@ -9,24 +8,28 @@ _lookup_env :: proc(key: string, allocator := context.allocator) -> (value: stri return } wkey := win32.utf8_to_wstring(key) - b := make([dynamic]u16, 100, context.temp_allocator) - for { - n := win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) - if n == 0 { - err := win32.GetLastError() - if err == win32.ERROR_ENVVAR_NOT_FOUND { - return "", false - } - } - if n <= u32(len(b)) { - value = win32.utf16_to_utf8(b[:n], allocator) - found = true - return + n := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n == 0 { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false } - - resize(&b, len(b)*2) + return "", true } + b := make([]u16, n+1, context.temp_allocator) + + n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) + if n == 0 { + err := win32.GetLastError() + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false + } + } + + value = win32.utf16_to_utf8(b[:n], allocator) + found = true + return } _set_env :: proc(key, value: string) -> bool { @@ -62,13 +65,12 @@ _environ :: proc(allocator := context.allocator) -> []string { r := make([dynamic]string, 0, 50, allocator) for from, i, p := 0, 0, envs; true; i += 1 { - c := (^u16)(uintptr(p) + uintptr(i*2))^ + c := ([^]u16)(p)[i] if c == 0 { if i <= from { break } - w := mem.slice_ptr(mem.ptr_offset(p, from), i-from) - + w := ([^]u16)(p)[from:i] append(&r, win32.utf16_to_utf8(w, allocator)) from = i + 1 } diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 69aeaee4b..6ddae74b1 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -11,6 +11,8 @@ General_Error :: enum u32 { Closed, Timeout, + + Invalid_File, } Platform_Error :: struct { @@ -24,36 +26,6 @@ Error :: union { } #assert(size_of(Error) == size_of(u64)) -Path_Error :: struct { - op: string, - path: string, - err: Error, -} - -Link_Error :: struct { - op: string, - old: string, - new: string, - err: Error, -} - -path_error_delete :: proc(perr: Maybe(Path_Error)) { - if err, ok := perr.?; ok { - context.allocator = error_allocator() - delete(err.op) - delete(err.path) - } -} - -link_error_delete :: proc(lerr: Maybe(Link_Error)) { - if err, ok := lerr.?; ok { - context.allocator = error_allocator() - delete(err.op) - delete(err.old) - delete(err.new) - } -} - is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) { diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 5b995bd69..0ed08c850 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -3,7 +3,9 @@ package os2 import "core:io" import "core:time" -Handle :: distinct uintptr +File :: struct { + impl: _File, +} Seek_From :: enum { Start = 0, // seek relative to the origin of the file @@ -19,119 +21,137 @@ File_Mode_Char_Device :: File_Mode(1<<19) File_Mode_Sym_Link :: File_Mode(1<<20) -O_RDONLY :: int( 0) -O_WRONLY :: int( 1) -O_RDWR :: int( 2) -O_APPEND :: int( 4) -O_CREATE :: int( 8) -O_EXCL :: int(16) -O_SYNC :: int(32) -O_TRUNC :: int(64) - - - -stdin: Handle = 0 // OS-Specific -stdout: Handle = 1 // OS-Specific -stderr: Handle = 2 // OS-Specific - - -create :: proc(name: string) -> (Handle, Error) { - return _create(name) +File_Flags :: distinct bit_set[File_Flag; uint] +File_Flag :: enum { + Read, + Write, + Append, + Create, + Excl, + Sync, + Trunc, + Sparse, } -open :: proc(name: string) -> (Handle, Error) { - return _open(name) +O_RDONLY :: File_Flags{.Read} +O_WRONLY :: File_Flags{.Write} +O_RDWR :: File_Flags{.Read, .Write} +O_APPEND :: File_Flags{.Append} +O_CREATE :: File_Flags{.Create} +O_EXCL :: File_Flags{.Excl} +O_SYNC :: File_Flags{.Sync} +O_TRUNC :: File_Flags{.Trunc} +O_SPARSE :: File_Flags{.Sparse} + + + +stdin: ^File = nil // OS-Specific +stdout: ^File = nil // OS-Specific +stderr: ^File = nil // OS-Specific + + +create :: proc(name: string) -> (^File, Error) { + return open(name, {.Read, .Write, .Create}, File_Mode(0o777)) } -open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) { - return _open_file(name, flag, perm) +open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) { + return _open(name, flags, perm) } -close :: proc(fd: Handle) -> Error { - return _close(fd) +new_file :: proc(handle: uintptr, name: string) -> ^File { + return _new_file(handle, name) } -name :: proc(fd: Handle, allocator := context.allocator) -> string { - return _name(fd) -} - -seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { - return _seek(fd, offset, whence) -} - -read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return _read(fd, p) -} - -read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - return _read_at(fd, p, offset) -} - -read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { - return _read_from(fd, r) -} - -write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return _write(fd, p) -} - -write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - return _write_at(fd, p, offset) -} - -write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { - return _write_to(fd, w) -} - -file_size :: proc(fd: Handle) -> (n: i64, err: Error) { - return _file_size(fd) +fd :: proc(f: ^File) -> uintptr { + return _fd(f) } -sync :: proc(fd: Handle) -> Error { - return _sync(fd) +close :: proc(f: ^File) -> Error { + return _close(f) } -flush :: proc(fd: Handle) -> Error { - return _flush(fd) +name :: proc(f: ^File) -> string { + return _name(f) } -truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) { - return _truncate(fd, size) +seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + return _seek(f, offset, whence) } -remove :: proc(name: string) -> Maybe(Path_Error) { +read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + return _read(f, p) +} + +read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + return _read_at(f, p, offset) +} + +read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { + return _read_from(f, r) +} + +write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + return _write(f, p) +} + +write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + return _write_at(f, p, offset) +} + +write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { + return _write_to(f, w) +} + +file_size :: proc(f: ^File) -> (n: i64, err: Error) { + return _file_size(f) +} + + +sync :: proc(f: ^File) -> Error { + return _sync(f) +} + +flush :: proc(f: ^File) -> Error { + return _flush(f) +} + +truncate :: proc(f: ^File, size: i64) -> Error { + return _truncate(f, size) +} + +remove :: proc(name: string) -> Error { return _remove(name) } -rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) { +rename :: proc(old_path, new_path: string) -> Error { return _rename(old_path, new_path) } -link :: proc(old_name, new_name: string) -> Maybe(Link_Error) { +link :: proc(old_name, new_name: string) -> Error { return _link(old_name, new_name) } -symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) { +symlink :: proc(old_name, new_name: string) -> Error { return _symlink(old_name, new_name) } -read_link :: proc(name: string) -> (string, Maybe(Path_Error)) { +read_link :: proc(name: string) -> (string, Error) { return _read_link(name) } -chdir :: proc(fd: Handle) -> Error { - return _chdir(fd) +chdir :: proc(f: ^File) -> Error { + return _chdir(f) } -chmod :: proc(fd: Handle, mode: File_Mode) -> Error { - return _chmod(fd, mode) +chmod :: proc(f: ^File, mode: File_Mode) -> Error { + return _chmod(f, mode) } -chown :: proc(fd: Handle, uid, gid: int) -> Error { - return _chown(fd, uid, gid) +chown :: proc(f: ^File, uid, gid: int) -> Error { + return _chown(f, uid, gid) } @@ -140,7 +160,7 @@ lchown :: proc(name: string, uid, gid: int) -> Error { } -chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) { +chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return _chtimes(name, atime, mtime) } diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 0962ed59c..7b68d0e21 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -2,8 +2,8 @@ package os2 import "core:io" -file_to_stream :: proc(fd: Handle) -> (s: io.Stream) { - s.stream_data = rawptr(uintptr(fd)) +file_to_stream :: proc(f: ^File) -> (s: io.Stream) { + s.stream_data = f s.stream_vtable = _file_stream_vtable return } @@ -20,66 +20,66 @@ error_to_io_error :: proc(ferr: Error) -> io.Error { @(private) _file_stream_vtable := &io.Stream_VTable{ impl_read = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read(fd, p) + n, ferr = read(f, p) err = error_to_io_error(ferr) return }, impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read_at(fd, p, offset) + n, ferr = read_at(f, p, offset) err = error_to_io_error(ferr) return }, impl_write_to = proc(s: io.Stream, w: io.Writer) -> (n: i64, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write_to(fd, w) + n, ferr = write_to(f, w) err = error_to_io_error(ferr) return }, impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write(fd, p) + n, ferr = write(f, p) err = error_to_io_error(ferr) return }, impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = write_at(fd, p, offset) + n, ferr = write_at(f, p, offset) err = error_to_io_error(ferr) return }, impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) { - fd := Handle(uintptr(s.stream_data)) + f := (^File)(s.stream_data) ferr: Error - n, ferr = read_from(fd, r) + n, ferr = read_from(f, r) err = error_to_io_error(ferr) return }, impl_seek = proc(s: io.Stream, offset: i64, whence: io.Seek_From) -> (i64, io.Error) { - fd := Handle(uintptr(s.stream_data)) - n, ferr := seek(fd, offset, Seek_From(whence)) + f := (^File)(s.stream_data) + n, ferr := seek(f, offset, Seek_From(whence)) err := error_to_io_error(ferr) return n, err }, impl_size = proc(s: io.Stream) -> i64 { - fd := Handle(uintptr(s.stream_data)) - sz, _ := file_size(fd) + f := (^File)(s.stream_data) + sz, _ := file_size(f) return sz }, impl_flush = proc(s: io.Stream) -> io.Error { - fd := Handle(uintptr(s.stream_data)) - ferr := flush(fd) + f := (^File)(s.stream_data) + ferr := flush(f) return error_to_io_error(ferr) }, impl_close = proc(s: io.Stream) -> io.Error { - fd := Handle(uintptr(s.stream_data)) - ferr := close(fd) + f := (^File)(s.stream_data) + ferr := close(f) return error_to_io_error(ferr) }, } diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 77f6545ac..f82bf73d0 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -4,25 +4,25 @@ import "core:mem" import "core:strconv" import "core:unicode/utf8" -write_string :: proc(fd: Handle, s: string) -> (n: int, err: Error) { - return write(fd, transmute([]byte)s) +write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) { + return write(f, transmute([]byte)s) } -write_byte :: proc(fd: Handle, b: byte) -> (n: int, err: Error) { - return write(fd, []byte{b}) +write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { + return write(f, []byte{b}) } -write_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { +write_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { if r < utf8.RUNE_SELF { - return write_byte(fd, byte(r)) + return write_byte(f, byte(r)) } b: [4]byte b, n = utf8.encode_rune(r) - return write(fd, b[:n]) + return write(f, b[:n]) } -write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { +write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { wrap :: proc(m: int, merr: Error, n: ^int, err: ^Error) -> bool { n^ += m if merr != nil { @@ -32,44 +32,44 @@ write_encoded_rune :: proc(fd: Handle, r: rune) -> (n: int, err: Error) { return false } - if wrap(write_byte(fd, '\''), &n, &err) { return } + if wrap(write_byte(f, '\''), &n, &err) { return } switch r { - case '\a': if wrap(write_string(fd, "\\a"), &n, &err) { return } - case '\b': if wrap(write_string(fd, "\\b"), &n, &err) { return } - case '\e': if wrap(write_string(fd, "\\e"), &n, &err) { return } - case '\f': if wrap(write_string(fd, "\\f"), &n, &err) { return } - case '\n': if wrap(write_string(fd, "\\n"), &n, &err) { return } - case '\r': if wrap(write_string(fd, "\\r"), &n, &err) { return } - case '\t': if wrap(write_string(fd, "\\t"), &n, &err) { return } - case '\v': if wrap(write_string(fd, "\\v"), &n, &err) { return } + case '\a': if wrap(write_string(f, "\\a"), &n, &err) { return } + case '\b': if wrap(write_string(f, "\\b"), &n, &err) { return } + case '\e': if wrap(write_string(f, "\\e"), &n, &err) { return } + case '\f': if wrap(write_string(f, "\\f"), &n, &err) { return } + case '\n': if wrap(write_string(f, "\\n"), &n, &err) { return } + case '\r': if wrap(write_string(f, "\\r"), &n, &err) { return } + case '\t': if wrap(write_string(f, "\\t"), &n, &err) { return } + case '\v': if wrap(write_string(f, "\\v"), &n, &err) { return } case: if r < 32 { - if wrap(write_string(fd, "\\x"), &n, &err) { return } + if wrap(write_string(f, "\\x"), &n, &err) { return } b: [2]byte s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { - case 0: if wrap(write_string(fd, "00"), &n, &err) { return } - case 1: if wrap(write_rune(fd, '0'), &n, &err) { return } - case 2: if wrap(write_string(fd, s), &n, &err) { return } + case 0: if wrap(write_string(f, "00"), &n, &err) { return } + case 1: if wrap(write_rune(f, '0'), &n, &err) { return } + case 2: if wrap(write_string(f, s), &n, &err) { return } } } else { - if wrap(write_rune(fd, r), &n, &err) { return } + if wrap(write_rune(f, r), &n, &err) { return } } } - _ = wrap(write_byte(fd, '\''), &n, &err) + _ = wrap(write_byte(f, '\''), &n, &err) return } -write_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { +write_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { s := transmute([]byte)mem.Raw_Slice{data, len} - return write(fd, s) + return write(f, s) } -read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { +read_ptr :: proc(f: ^File, data: rawptr, len: int) -> (n: int, err: Error) { s := transmute([]byte)mem.Raw_Slice{data, len} - return read(fd, s) + return read(f, s) } @@ -109,7 +109,7 @@ write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate if truncate { flags |= O_TRUNC } - f, err := open_file(name, flags, perm) + f, err := open(name, flags, perm) if err != nil { return err } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index cfc9feebf..880305830 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -3,103 +3,260 @@ package os2 import "core:io" import "core:time" +import "core:runtime" +import "core:strings" +import win32 "core:sys/windows" -_create :: proc(name: string) -> (Handle, Error) { - return 0, nil +INVALID_HANDLE :: ~uintptr(0) + +_file_allocator :: proc() -> runtime.Allocator { + return heap_allocator() } -_open :: proc(name: string) -> (Handle, Error) { - return 0, nil +_File_Kind :: enum u8 { + File, + Console, + Pipe, } -_open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) { - return 0, nil +_File :: struct { + fd: rawptr, + name: string, + wname: win32.wstring, + kind: _File_Kind, } -_close :: proc(fd: Handle) -> Error { +_get_platform_error :: proc() -> Error { + err := i32(win32.GetLastError()) + if err != 0 { + return Platform_Error{err} + } return nil } -_name :: proc(fd: Handle, allocator := context.allocator) -> string { - return "" +_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) { + return nil, nil } -_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { - return +_new_file :: proc(handle: uintptr, name: string) -> ^File { + if handle == INVALID_HANDLE { + return nil + } + context.allocator = _file_allocator() + f := new(File) + f.impl.fd = rawptr(fd) + f.impl.name = strings.clone(name, context.allocator) + f.impl.wname = win32.utf8_to_wstring(name, context.allocator) + + kind := _File_Kind.File + if m: u32; win32.GetConsoleMode(win32.HANDLE(fd), &m) { + kind = .Console + } + if win32.GetFileType(win32.HANDLE(fd)) == win32.FILE_TYPE_PIPE { + kind = .Pipe + } + f.impl.kind = kind + + return f } -_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return +_fd :: proc(f: ^File) -> uintptr { + if f == nil { + return INVALID_HANDLE + } + return uintptr(f.impl.fd) } -_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - return -} +_destroy :: proc(f: ^File) -> Error { + if f == nil { + return nil + } -_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { - return -} - -_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { - return -} - -_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { - return -} - -_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { - return -} - -_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { - return -} - - -_sync :: proc(fd: Handle) -> Error { - return nil -} - -_flush :: proc(fd: Handle) -> Error { - return nil -} - -_truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) { - return nil -} - -_remove :: proc(name: string) -> Maybe(Path_Error) { - return nil -} - -_rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) { + context.allocator = _file_allocator() + free(f.impl.wname) + delete(f.impl.name) + free(f) return nil } -_link :: proc(old_name, new_name: string) -> Maybe(Link_Error) { +_close :: proc(f: ^File) -> Error { + if f == nil { + return nil + } + if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) { + return .Closed + } + return _destroy(f) +} + +_name :: proc(f: ^File) -> string { + return f.impl.name if f != nil else "" +} + +_seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + if f == nil { + return + } + w: u32 + switch whence { + case .Start: w = win32.FILE_BEGIN + case .Current: w = win32.FILE_CURRENT + case .End: w = win32.FILE_END + } + hi := i32(offset>>32) + lo := i32(offset) + ft := win32.GetFileType(win32.HANDLE(fd)) + if ft == win32.FILE_TYPE_PIPE { + return 0, .Invalid_File + } + + dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) + if dw_ptr == win32.INVALID_SET_FILE_POINTER { + return 0, _get_platform_error() + } + return i64(hi)<<32 + i64(dw_ptr), nil +} + +_read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + return +} + +_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + return +} + +_read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { + return +} + +_write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { + return +} + +_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + return +} + +_write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { + return +} + +_file_size :: proc(f: ^File) -> (n: i64, err: Error) { + if f == nil { + return + } + length: win32.LARGE_INTEGER + if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { + err = _get_platform_error() + } + n = i64(length) + return +} + + +_sync :: proc(f: ^File) -> Error { return nil } -_symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) { +_flush :: proc(f: ^File) -> Error { return nil } -_read_link :: proc(name: string) -> (string, Maybe(Path_Error)) { +_truncate :: proc(f: ^File, size: i64) -> Error { + if f == nil { + return nil + } + curr_off := seek(f, 0, .Current) or_return + defer seek(f, curr_off, .Start) + seek(f, size, .Start) or_return + if !win32.SetEndOfFile(win32.HANDLE(fd)) { + return _get_platform_error() + } + return nil +} + +_remove :: proc(name: string) -> Error { + p := _fix_long_path(name) + err, err1: Error + if !win32.DeleteFileW(p) { + err = _get_platform_error() + } + if err == nil { + return nil + } + if !win32.RemoveDirectoryW(p) { + err1 = _get_platform_error() + } + if err1 == nil { + return nil + } + + if err != err1 { + a := win32.GetFileAttributesW(p) + if a == ~u32(0) { + err = _get_platform_error() + } else { + if a & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + err = err1 + } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { + if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { + err = nil + if !win32.DeleteFileW(p) { + err = _get_platform_error() + } + } + } + } + } + + return err +} + +_rename :: proc(old_path, new_path: string) -> Error { + from := _fix_long_path(old_path) + to := _fix_long_path(new_path) + if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { + return nil + } + return _get_platform_error() + +} + + +_link :: proc(old_name, new_name: string) -> Error { + o := _fix_long_path(old_name) + n := _fix_long_path(new_name) + if win32.CreateHardLinkW(n, o, nil) { + return nil + } + return _get_platform_error() +} + +_symlink :: proc(old_name, new_name: string) -> Error { + return nil +} + +_read_link :: proc(name: string) -> (string, Error) { return "", nil } -_chdir :: proc(fd: Handle) -> Error { +_chdir :: proc(f: ^File) -> Error { + if f == nil { + return nil + } + if win32.SetCurrentDirectoryW(f.impl.wname) { + return nil + } + return _get_platform_error() +} + +_chmod :: proc(f: ^File, mode: File_Mode) -> Error { return nil } -_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { - return nil -} - -_chown :: proc(fd: Handle, uid, gid: int) -> Error { +_chown :: proc(f: ^File, uid, gid: int) -> Error { return nil } @@ -109,28 +266,31 @@ _lchown :: proc(name: string, uid, gid: int) -> Error { } -_chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) { +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return nil } _exists :: proc(path: string) -> bool { - return false + wpath := _fix_long_path(path) + attribs := win32.GetFileAttributesW(wpath) + return i32(attribs) != win32.INVALID_FILE_ATTRIBUTES } _is_file :: proc(path: string) -> bool { + wpath := _fix_long_path(path) + attribs := win32.GetFileAttributesW(wpath) + if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 + } return false } _is_dir :: proc(path: string) -> bool { + wpath := _fix_long_path(path) + attribs := win32.GetFileAttributesW(wpath) + if i32(attribs) != win32.INVALID_FILE_ATTRIBUTES { + return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 + } return false } - - -_path_error_delete :: proc(perr: Maybe(Path_Error)) { - -} - -_link_error_delete :: proc(lerr: Maybe(Link_Error)) { - -} diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index ee7d6e6f2..eca8b518f 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -7,15 +7,15 @@ is_path_separator :: proc(c: byte) -> bool { return _is_path_separator(c) } -mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) { +mkdir :: proc(name: string, perm: File_Mode) -> Error { return _mkdir(name, perm) } -mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) { +mkdir_all :: proc(path: string, perm: File_Mode) -> Error { return _mkdir_all(path, perm) } -remove_all :: proc(path: string) -> Maybe(Path_Error) { +remove_all :: proc(path: string) -> Error { return _remove_all(path) } diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 607f56968..0e77db4fd 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -1,6 +1,8 @@ //+private package os2 +import win32 "core:sys/windows" + _Path_Separator :: '\\' _Path_List_Separator :: ';' @@ -8,16 +10,16 @@ _is_path_separator :: proc(c: byte) -> bool { return c == '\\' || c == '/' } -_mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) { +_mkdir :: proc(name: string, perm: File_Mode) -> Error { return nil } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) { +_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { // TODO(bill): _mkdir_all for windows return nil } -_remove_all :: proc(path: string) -> Maybe(Path_Error) { +_remove_all :: proc(path: string) -> Error { // TODO(bill): _remove_all for windows return nil } @@ -29,3 +31,81 @@ _getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { _setwd :: proc(dir: string) -> (err: Error) { return nil } + + +can_use_long_paths: bool + +@(init) +init_long_path_support :: proc() { + // TODO(bill): init_long_path_support + // ADD THIS SHIT + // registry_path := win32.L(`Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled`) + can_use_long_paths = false +} + + +_fix_long_path_slice :: proc(path: string) -> []u16 { + return win32.utf8_to_utf16(_fix_long_path_internal(path)) +} + +_fix_long_path :: proc(path: string) -> win32.wstring { + return win32.utf8_to_wstring(_fix_long_path_internal(path)) +} + + +_fix_long_path_internal :: proc(path: string) -> string { + if can_use_long_paths { + return path + } + + // When using win32 to create a directory, the path + // cannot be too long that you cannot append an 8.3 + // file name, because MAX_PATH is 260, 260-12 = 248 + if len(path) < 248 { + return path + } + + // UNC paths do not need to be modified + if len(path) >= 2 && path[:2] == `\\` { + return path + } + + if !_is_abs(path) { // relative path + return path + } + + PREFIX :: `\\?` + path_buf := make([]byte, len(PREFIX)+len(path)+1, context.temp_allocator) + copy(path_buf, PREFIX) + n := len(path) + r, w := 0, len(PREFIX) + for r < n { + switch { + case is_path_separator(path[r]): + r += 1 + case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): + // \.\ + r += 1 + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): + // Skip \..\ paths + return path + case: + path_buf[w] = '\\' + w += 1 + for r < n && !is_path_separator(path[r]) { + path_buf[w] = path[r] + r += 1 + w += 1 + } + } + } + + // Root directories require a trailing \ + if w == len(`\\?\c:`) { + path_buf[w] = '\\' + w += 1 + } + + return string(path_buf[:w]) + +} diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin index c38f03f03..62f7ddf10 100644 --- a/core/os/os2/pipe.odin +++ b/core/os/os2/pipe.odin @@ -1,5 +1,5 @@ package os2 -pipe :: proc() -> (r, w: Handle, err: Error) { +pipe :: proc() -> (r, w: ^File, err: Error) { return _pipe() } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index 5570ca282..ddb54f80c 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -3,11 +3,11 @@ package os2 import win32 "core:sys/windows" -_pipe :: proc() -> (r, w: Handle, err: Error) { +_pipe :: proc() -> (r, w: ^File, err: Error) { p: [2]win32.HANDLE if !win32.CreatePipe(&p[0], &p[1], nil, 0) { - return 0, 0, Platform_Error{i32(win32.GetLastError())} + return nil, nil, Platform_Error{i32(win32.GetLastError())} } - return Handle(p[0]), Handle(p[1]), nil + return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil } diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 7fc7a4ac0..db47e2f5b 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -46,7 +46,7 @@ Process :: struct { Process_Attributes :: struct { dir: string, env: []string, - files: []Handle, + files: []^File, sys: ^Process_Attributes_OS_Specific, } diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 19f1453ff..63f5a17e8 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -24,15 +24,15 @@ file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { delete(fi.fullpath, allocator) } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { - return _fstat(fd, allocator) +fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) { + return _fstat(f, allocator) } -stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return _stat(name, allocator) } -lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return _lstat(name, allocator) } diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index f46a9435c..a79e5aae2 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -2,20 +2,21 @@ package os2 import "core:time" +import "core:strings" import win32 "core:sys/windows" -_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { - if fd == 0 { - return {}, Path_Error{err = .Invalid_Argument} +_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) { + if f == nil || f.impl.fd == nil { + return {}, .Invalid_Argument } context.allocator = allocator - path, err := _cleanpath_from_handle(fd) + path, err := _cleanpath_from_handle(f) if err != nil { return {}, err } - h := win32.HANDLE(fd) + h := win32.HANDLE(f.impl.fd) switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: fi: File_Info @@ -27,10 +28,10 @@ _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe( return _file_info_from_get_file_information_by_handle(path, h) } -_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS) } -_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT) } _same_file :: proc(fi1, fi2: File_Info) -> bool { @@ -39,12 +40,12 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { -_stat_errno :: proc(errno: win32.DWORD) -> Path_Error { - return Path_Error{err = Platform_Error{i32(errno)}} +_stat_errno :: proc(errno: win32.DWORD) -> Error { + return Platform_Error{i32(errno)} } -full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Maybe(Path_Error)) { +full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Error) { context.allocator = allocator name := name @@ -69,15 +70,15 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa } -internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Maybe(Path_Error)) { +internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { - return {}, Path_Error{err = .Not_Exist} + return {}, .Not_Exist } context.allocator = allocator - wname := win32.utf8_to_wstring(_fix_long_path(name), context.temp_allocator) + wname := _fix_long_path(name) fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { @@ -91,7 +92,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co fd: win32.WIN32_FIND_DATAW sh := win32.FindFirstFileW(wname, &fd) if sh == win32.INVALID_HANDLE_VALUE { - e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}} + e = _get_platform_error() return } win32.FindClose(sh) @@ -101,7 +102,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) if h == win32.INVALID_HANDLE_VALUE { - e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}} + e = _get_platform_error() return } defer win32.CloseHandle(h) @@ -130,11 +131,11 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { } -_cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { - if fd == 0 { - return "", Path_Error{err = .Invalid_Argument} +_cleanpath_from_handle :: proc(f: ^File) -> (string, Error) { + if f == nil || f.impl.fd == nil { + return "", .Invalid_Argument } - h := win32.HANDLE(fd) + h := win32.HANDLE(f.impl.fd) MAX_PATH := win32.DWORD(260) + 1 buf: []u16 @@ -153,11 +154,11 @@ _cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { return _cleanpath_from_buf(buf), nil } -_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) { - if fd == 0 { - return nil, Path_Error{err = .Invalid_Argument} +_cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { + if f == nil || f.impl.fd == nil { + return nil, .Invalid_Argument } - h := win32.HANDLE(fd) + h := win32.HANDLE(f.impl.fd) MAX_PATH := win32.DWORD(260) + 1 buf: []u16 @@ -251,7 +252,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA } -_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { +_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -268,7 +269,7 @@ _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE } -_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { +_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -285,7 +286,7 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string } -_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Maybe(Path_Error)) { +_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { return {}, _stat_errno(win32.GetLastError()) @@ -318,58 +319,83 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA return fi, nil } -_is_abs :: proc(path: string) -> bool { - if len(path) > 0 && path[0] == '/' { - return true + + +reserved_names := [?]string{ + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", +} + +_is_reserved_name :: proc(path: string) -> bool { + if len(path) == 0 { + return false } - if len(path) > 2 { - switch path[0] { - case 'A'..='Z', 'a'..='z': - return path[1] == ':' && is_path_separator(path[2]) + for reserved in reserved_names { + if strings.equal_fold(path, reserved) { + return true } } return false } -_fix_long_path :: proc(path: string) -> string { - if len(path) < 248 { - return path - } +_is_UNC :: proc(path: string) -> bool { + return _volume_name_len(path) > 2 +} - if len(path) >= 2 && path[:2] == `\\` { - return path - } - if !_is_abs(path) { - return path - } +_volume_name_len :: proc(path: string) -> int { + if ODIN_OS == .Windows { + if len(path) < 2 { + return 0 + } + c := path[0] + if path[1] == ':' { + switch c { + case 'a'..='z', 'A'..='Z': + return 2 + } + } - prefix :: `\\?` - - path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator) - copy(path_buf, prefix) - n := len(path) - r, w := 0, len(prefix) - for r < n { - switch { - case is_path_separator(path[r]): - r += 1 - case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): - r += 1 - case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): - return path - case: - path_buf[w] = '\\' - w += 1 - for ; r < n && !is_path_separator(path[r]); r += 1 { - path_buf[w] = path[r] - w += 1 + // URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) && + !_is_path_separator(path[2]) && path[2] != '.' { + for n := 3; n < l-1; n += 1 { + if _is_path_separator(path[n]) { + n += 1 + if !_is_path_separator(path[n]) { + if path[n] == '.' { + break + } + } + for ; n < l; n += 1 { + if _is_path_separator(path[n]) { + break + } + } + return n + } + break } } } - - if w == len(`\\?\c:`) { - path_buf[w] = '\\' - w += 1 - } - return string(path_buf[:w]) + return 0 } + + +_is_abs :: proc(path: string) -> bool { + if _is_reserved_name(path) { + return true + } + l := _volume_name_len(path) + if l == 0 { + return false + } + + path := path + path = path[l:] + if path == "" { + return false + } + return is_path_separator(path[0]) +} + diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 8ff0e1656..90131699d 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -1,7 +1,7 @@ package os2 -create_temp :: proc(dir, pattern: string) -> (Handle, Error) { +create_temp :: proc(dir, pattern: string) -> (^File, Error) { return _create_temp(dir, pattern) } diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 43f9f43b4..17967393a 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -3,8 +3,8 @@ package os2 import win32 "core:sys/windows" -_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { - return 0, nil +_create_temp :: proc(dir, pattern: string) -> (^File, Error) { + return nil, nil } _mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index ae5336849..21e9e54f4 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -163,9 +163,6 @@ O_SYNC :: 0x0080 O_ASYNC :: 0x0040 O_CLOEXEC :: 0x1000000 -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 SEEK_DATA :: 3 SEEK_HOLE :: 4 SEEK_MAX :: SEEK_HOLE diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 4a95028c1..6545423d4 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -123,9 +123,6 @@ O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 SEEK_DATA :: 3 SEEK_HOLE :: 4 SEEK_MAX :: SEEK_HOLE diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index ed73341c0..9b712cecc 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -167,9 +167,6 @@ O_ASYNC :: 0x02000 O_CLOEXEC :: 0x80000 -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 SEEK_DATA :: 3 SEEK_HOLE :: 4 SEEK_MAX :: SEEK_HOLE diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index a99c8fef0..dd230f9b5 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -125,10 +125,6 @@ O_EXCL :: 0x00800 O_NOCTTY :: 0x08000 O_CLOEXEC :: 0x10000 -SEEK_SET :: 0 -SEEK_CUR :: 1 -SEEK_END :: 2 - RTLD_LAZY :: 0x001 RTLD_NOW :: 0x002 RTLD_LOCAL :: 0x000 diff --git a/core/sys/win32/general.odin b/core/sys/win32/general.odin index d53bf8a4f..1baad8b11 100644 --- a/core/sys/win32/general.odin +++ b/core/sys/win32/general.odin @@ -30,7 +30,7 @@ Monitor_Enum_Proc :: distinct #type proc "std" (Hmonitor, Hdc, ^Rect, Lparam) -> Bool :: distinct b32 -Wstring :: distinct ^u16 +Wstring :: distinct [^]u16 Point :: struct { x, y: i32, diff --git a/core/sys/win32/kernel32.odin b/core/sys/win32/kernel32.odin index 709964fb4..ca6122690 100644 --- a/core/sys/win32/kernel32.odin +++ b/core/sys/win32/kernel32.odin @@ -234,4 +234,4 @@ PAGE_WRITECOPY :: 0x08 PAGE_EXECUTE :: 0x10 PAGE_EXECUTE_READ :: 0x20 PAGE_EXECUTE_READWRITE :: 0x40 -PAGE_EXECUTE_WRITECOPY :: 0x80 +PAGE_EXECUTE_WRITECOPY :: 0x80 \ No newline at end of file diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index e728503d2..898766361 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -67,8 +67,6 @@ foreign gdi32 { PatBlt :: proc(hdc: HDC, x, y, w, h: c_int, rop: DWORD) -> BOOL --- } -// Windows colors are packed as ABGR RGB :: #force_inline proc "contextless" (r, g, b: u8) -> COLORREF { - res: [4]u8 = {0, b, g, r} - return transmute(COLORREF)res + return transmute(COLORREF)[4]u8{r, g, b, 0} } diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index cb90f71da..d95ff91c9 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -238,6 +238,7 @@ foreign kernel32 { bInitialState: BOOL, lpName: LPCWSTR, ) -> HANDLE --- + ResetEvent :: proc(hEvent: HANDLE) -> BOOL --- WaitForMultipleObjects :: proc( nCount: DWORD, lpHandles: ^HANDLE, @@ -266,6 +267,17 @@ foreign kernel32 { HeapReAlloc :: proc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID, dwBytes: SIZE_T) -> LPVOID --- HeapFree :: proc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL --- + ReadDirectoryChangesW :: proc( + hDirectory: HANDLE, + lpBuffer: LPVOID, + nBufferLength: DWORD, + bWatchSubtree: BOOL, + dwNotifyFilter: DWORD, + lpBytesReturned: LPDWORD, + lpOverlapped: LPOVERLAPPED, + lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE, + ) -> BOOL --- + InitializeSRWLock :: proc(SRWLock: ^SRWLOCK) --- AcquireSRWLockExclusive :: proc(SRWLock: ^SRWLOCK) --- TryAcquireSRWLockExclusive :: proc(SRWLock: ^SRWLOCK) -> BOOL --- @@ -758,3 +770,18 @@ foreign kernel32 { UnmapFlags: ULONG, ) -> BOOL --- } + +@(default_calling_convention = "std") +foreign kernel32 { + @(link_name="SetConsoleCtrlHandler") set_console_ctrl_handler :: proc(handler: Handler_Routine, add: BOOL) -> BOOL --- +} + +Handler_Routine :: proc(dwCtrlType: Control_Event) -> BOOL + +Control_Event :: enum DWORD { + control_c = 0, + _break = 1, + close = 2, + logoff = 5, + shutdown = 6, +} \ No newline at end of file diff --git a/core/sys/windows/system_params.odin b/core/sys/windows/system_params.odin new file mode 100644 index 000000000..e94d777bf --- /dev/null +++ b/core/sys/windows/system_params.odin @@ -0,0 +1,271 @@ +// +build windows +package sys_windows + +// Parameter for SystemParametersInfo. +SPI_GETBEEP :: 0x0001 +SPI_SETBEEP :: 0x0002 +SPI_GETMOUSE :: 0x0003 +SPI_SETMOUSE :: 0x0004 +SPI_GETBORDER :: 0x0005 +SPI_SETBORDER :: 0x0006 +SPI_GETKEYBOARDSPEED :: 0x000A +SPI_SETKEYBOARDSPEED :: 0x000B +SPI_LANGDRIVER :: 0x000C +SPI_ICONHORIZONTALSPACING :: 0x000D +SPI_GETSCREENSAVETIMEOUT :: 0x000E +SPI_SETSCREENSAVETIMEOUT :: 0x000F +SPI_GETSCREENSAVEACTIVE :: 0x0010 +SPI_SETSCREENSAVEACTIVE :: 0x0011 +SPI_GETGRIDGRANULARITY :: 0x0012 +SPI_SETGRIDGRANULARITY :: 0x0013 +SPI_SETDESKWALLPAPER :: 0x0014 +SPI_SETDESKPATTERN :: 0x0015 +SPI_GETKEYBOARDDELAY :: 0x0016 +SPI_SETKEYBOARDDELAY :: 0x0017 +SPI_ICONVERTICALSPACING :: 0x0018 +SPI_GETICONTITLEWRAP :: 0x0019 +SPI_SETICONTITLEWRAP :: 0x001A +SPI_GETMENUDROPALIGNMENT :: 0x001B +SPI_SETMENUDROPALIGNMENT :: 0x001C +SPI_SETDOUBLECLKWIDTH :: 0x001D +SPI_SETDOUBLECLKHEIGHT :: 0x001E +SPI_GETICONTITLELOGFONT :: 0x001F +SPI_SETDOUBLECLICKTIME :: 0x0020 +SPI_SETMOUSEBUTTONSWAP :: 0x0021 +SPI_SETICONTITLELOGFONT :: 0x0022 +SPI_GETFASTTASKSWITCH :: 0x0023 +SPI_SETFASTTASKSWITCH :: 0x0024 + +SPI_SETDRAGFULLWINDOWS :: 0x0025 +SPI_GETDRAGFULLWINDOWS :: 0x0026 +SPI_GETNONCLIENTMETRICS :: 0x0029 +SPI_SETNONCLIENTMETRICS :: 0x002A +SPI_GETMINIMIZEDMETRICS :: 0x002B +SPI_SETMINIMIZEDMETRICS :: 0x002C +SPI_GETICONMETRICS :: 0x002D +SPI_SETICONMETRICS :: 0x002E +SPI_SETWORKAREA :: 0x002F +SPI_GETWORKAREA :: 0x0030 +SPI_SETPENWINDOWS :: 0x0031 +SPI_GETHIGHCONTRAST :: 0x0042 +SPI_SETHIGHCONTRAST :: 0x0043 +SPI_GETKEYBOARDPREF :: 0x0044 +SPI_SETKEYBOARDPREF :: 0x0045 +SPI_GETSCREENREADER :: 0x0046 +SPI_SETSCREENREADER :: 0x0047 +SPI_GETANIMATION :: 0x0048 +SPI_SETANIMATION :: 0x0049 +SPI_GETFONTSMOOTHING :: 0x004A +SPI_SETFONTSMOOTHING :: 0x004B +SPI_SETDRAGWIDTH :: 0x004C +SPI_SETDRAGHEIGHT :: 0x004D +SPI_SETHANDHELD :: 0x004E +SPI_GETLOWPOWERTIMEOUT :: 0x004F +SPI_GETPOWEROFFTIMEOUT :: 0x0050 +SPI_SETLOWPOWERTIMEOUT :: 0x0051 +SPI_SETPOWEROFFTIMEOUT :: 0x0052 +SPI_GETLOWPOWERACTIVE :: 0x0053 +SPI_GETPOWEROFFACTIVE :: 0x0054 +SPI_SETLOWPOWERACTIVE :: 0x0055 +SPI_SETPOWEROFFACTIVE :: 0x0056 +SPI_SETCURSORS :: 0x0057 +SPI_SETICONS :: 0x0058 +SPI_GETDEFAULTINPUTLANG :: 0x0059 +SPI_SETDEFAULTINPUTLANG :: 0x005A +SPI_SETLANGTOGGLE :: 0x005B +SPI_GETWINDOWSEXTENSION :: 0x005C +SPI_SETMOUSETRAILS :: 0x005D +SPI_GETMOUSETRAILS :: 0x005E +SPI_SETSCREENSAVERRUNNING :: 0x0061 + +SPI_SCREENSAVERRUNNING :: SPI_SETSCREENSAVERRUNNING +SPI_GETFILTERKEYS :: 0x0032 +SPI_SETFILTERKEYS :: 0x0033 +SPI_GETTOGGLEKEYS :: 0x0034 +SPI_SETTOGGLEKEYS :: 0x0035 +SPI_GETMOUSEKEYS :: 0x0036 +SPI_SETMOUSEKEYS :: 0x0037 +SPI_GETSHOWSOUNDS :: 0x0038 +SPI_SETSHOWSOUNDS :: 0x0039 +SPI_GETSTICKYKEYS :: 0x003A +SPI_SETSTICKYKEYS :: 0x003B +SPI_GETACCESSTIMEOUT :: 0x003C +SPI_SETACCESSTIMEOUT :: 0x003D +SPI_GETSERIALKEYS :: 0x003E +SPI_SETSERIALKEYS :: 0x003F +SPI_GETSOUNDSENTRY :: 0x0040 +SPI_SETSOUNDSENTRY :: 0x0041 +SPI_GETSNAPTODEFBUTTON :: 0x005F +SPI_SETSNAPTODEFBUTTON :: 0x0060 +SPI_GETMOUSEHOVERWIDTH :: 0x0062 +SPI_SETMOUSEHOVERWIDTH :: 0x0063 +SPI_GETMOUSEHOVERHEIGHT :: 0x0064 +SPI_SETMOUSEHOVERHEIGHT :: 0x0065 +SPI_GETMOUSEHOVERTIME :: 0x0066 +SPI_SETMOUSEHOVERTIME :: 0x0067 +SPI_GETWHEELSCROLLLINES :: 0x0068 +SPI_SETWHEELSCROLLLINES :: 0x0069 +SPI_GETMENUSHOWDELAY :: 0x006A +SPI_SETMENUSHOWDELAY :: 0x006B + +SPI_GETWHEELSCROLLCHARS :: 0x006C +SPI_SETWHEELSCROLLCHARS :: 0x006D +SPI_GETSHOWIMEUI :: 0x006E +SPI_SETSHOWIMEUI :: 0x006F +SPI_GETMOUSESPEED :: 0x0070 +SPI_SETMOUSESPEED :: 0x0071 +SPI_GETSCREENSAVERRUNNING :: 0x0072 +SPI_GETDESKWALLPAPER :: 0x0073 +SPI_GETAUDIODESCRIPTION :: 0x0074 +SPI_SETAUDIODESCRIPTION :: 0x0075 +SPI_GETSCREENSAVESECURE :: 0x0076 +SPI_SETSCREENSAVESECURE :: 0x0077 + +SPI_GETHUNGAPPTIMEOUT :: 0x0078 +SPI_SETHUNGAPPTIMEOUT :: 0x0079 +SPI_GETWAITTOKILLTIMEOUT :: 0x007A +SPI_SETWAITTOKILLTIMEOUT :: 0x007B +SPI_GETWAITTOKILLSERVICETIMEOUT :: 0x007C +SPI_SETWAITTOKILLSERVICETIMEOUT :: 0x007D +SPI_GETMOUSEDOCKTHRESHOLD :: 0x007E +SPI_SETMOUSEDOCKTHRESHOLD :: 0x007F +SPI_GETPENDOCKTHRESHOLD :: 0x0080 +SPI_SETPENDOCKTHRESHOLD :: 0x0081 +SPI_GETWINARRANGING :: 0x0082 +SPI_SETWINARRANGING :: 0x0083 +SPI_GETMOUSEDRAGOUTTHRESHOLD :: 0x0084 +SPI_SETMOUSEDRAGOUTTHRESHOLD :: 0x0085 +SPI_GETPENDRAGOUTTHRESHOLD :: 0x0086 +SPI_SETPENDRAGOUTTHRESHOLD :: 0x0087 +SPI_GETMOUSESIDEMOVETHRESHOLD :: 0x0088 +SPI_SETMOUSESIDEMOVETHRESHOLD :: 0x0089 +SPI_GETPENSIDEMOVETHRESHOLD :: 0x008A +SPI_SETPENSIDEMOVETHRESHOLD :: 0x008B +SPI_GETDRAGFROMMAXIMIZE :: 0x008C +SPI_SETDRAGFROMMAXIMIZE :: 0x008D +SPI_GETSNAPSIZING :: 0x008E +SPI_SETSNAPSIZING :: 0x008F +SPI_GETDOCKMOVING :: 0x0090 +SPI_SETDOCKMOVING :: 0x0091 + +SPI_GETACTIVEWINDOWTRACKING :: 0x1000 +SPI_SETACTIVEWINDOWTRACKING :: 0x1001 +SPI_GETMENUANIMATION :: 0x1002 +SPI_SETMENUANIMATION :: 0x1003 +SPI_GETCOMBOBOXANIMATION :: 0x1004 +SPI_SETCOMBOBOXANIMATION :: 0x1005 +SPI_GETLISTBOXSMOOTHSCROLLING :: 0x1006 +SPI_SETLISTBOXSMOOTHSCROLLING :: 0x1007 +SPI_GETGRADIENTCAPTIONS :: 0x1008 +SPI_SETGRADIENTCAPTIONS :: 0x1009 +SPI_GETKEYBOARDCUES :: 0x100A +SPI_SETKEYBOARDCUES :: 0x100B +SPI_GETMENUUNDERLINES :: SPI_GETKEYBOARDCUES +SPI_SETMENUUNDERLINES :: SPI_SETKEYBOARDCUES +SPI_GETACTIVEWNDTRKZORDER :: 0x100C +SPI_SETACTIVEWNDTRKZORDER :: 0x100D +SPI_GETHOTTRACKING :: 0x100E +SPI_SETHOTTRACKING :: 0x100F +SPI_GETMENUFADE :: 0x1012 +SPI_SETMENUFADE :: 0x1013 +SPI_GETSELECTIONFADE :: 0x1014 +SPI_SETSELECTIONFADE :: 0x1015 +SPI_GETTOOLTIPANIMATION :: 0x1016 +SPI_SETTOOLTIPANIMATION :: 0x1017 +SPI_GETTOOLTIPFADE :: 0x1018 +SPI_SETTOOLTIPFADE :: 0x1019 +SPI_GETCURSORSHADOW :: 0x101A +SPI_SETCURSORSHADOW :: 0x101B +SPI_GETMOUSESONAR :: 0x101C +SPI_SETMOUSESONAR :: 0x101D +SPI_GETMOUSECLICKLOCK :: 0x101E +SPI_SETMOUSECLICKLOCK :: 0x101F +SPI_GETMOUSEVANISH :: 0x1020 +SPI_SETMOUSEVANISH :: 0x1021 +SPI_GETFLATMENU :: 0x1022 +SPI_SETFLATMENU :: 0x1023 +SPI_GETDROPSHADOW :: 0x1024 +SPI_SETDROPSHADOW :: 0x1025 +SPI_GETBLOCKSENDINPUTRESETS :: 0x1026 +SPI_SETBLOCKSENDINPUTRESETS :: 0x1027 +SPI_GETUIEFFECTS :: 0x103E +SPI_SETUIEFFECTS :: 0x103F +SPI_GETDISABLEOVERLAPPEDCONTENT :: 0x1040 +SPI_SETDISABLEOVERLAPPEDCONTENT :: 0x1041 +SPI_GETCLIENTAREAANIMATION :: 0x1042 +SPI_SETCLIENTAREAANIMATION :: 0x1043 +SPI_GETCLEARTYPE :: 0x1048 +SPI_SETCLEARTYPE :: 0x1049 +SPI_GETSPEECHRECOGNITION :: 0x104A +SPI_SETSPEECHRECOGNITION :: 0x104B +SPI_GETCARETBROWSING :: 0x104C +SPI_SETCARETBROWSING :: 0x104D +SPI_GETTHREADLOCALINPUTSETTINGS :: 0x104E +SPI_SETTHREADLOCALINPUTSETTINGS :: 0x104F +SPI_GETSYSTEMLANGUAGEBAR :: 0x1050 +SPI_SETSYSTEMLANGUAGEBAR :: 0x1051 +SPI_GETFOREGROUNDLOCKTIMEOUT :: 0x2000 +SPI_SETFOREGROUNDLOCKTIMEOUT :: 0x2001 +SPI_GETACTIVEWNDTRKTIMEOUT :: 0x2002 +SPI_SETACTIVEWNDTRKTIMEOUT :: 0x2003 +SPI_GETFOREGROUNDFLASHCOUNT :: 0x2004 +SPI_SETFOREGROUNDFLASHCOUNT :: 0x2005 +SPI_GETCARETWIDTH :: 0x2006 +SPI_SETCARETWIDTH :: 0x2007 +SPI_GETMOUSECLICKLOCKTIME :: 0x2008 +SPI_SETMOUSECLICKLOCKTIME :: 0x2009 +SPI_GETFONTSMOOTHINGTYPE :: 0x200A +SPI_SETFONTSMOOTHINGTYPE :: 0x200B +// constants for SPI_GETFONTSMOOTHINGTYPE and SPI_SETFONTSMOOTHINGTYPE: +FE_FONTSMOOTHINGSTANDARD :: 0x0001 +FE_FONTSMOOTHINGCLEARTYPE :: 0x0002 + +SPI_GETFONTSMOOTHINGCONTRAST :: 0x200C +SPI_SETFONTSMOOTHINGCONTRAST :: 0x200D + +SPI_GETFOCUSBORDERWIDTH :: 0x200E +SPI_SETFOCUSBORDERWIDTH :: 0x200F +SPI_GETFOCUSBORDERHEIGHT :: 0x2010 +SPI_SETFOCUSBORDERHEIGHT :: 0x2011 + +SPI_GETFONTSMOOTHINGORIENTATION :: 0x2012 +SPI_SETFONTSMOOTHINGORIENTATION :: 0x2013 + +// constants for SPI_GETFONTSMOOTHINGORIENTATION and SPI_SETFONTSMOOTHINGORIENTATION: +FE_FONTSMOOTHINGORIENTATIONBGR :: 0x0000 +FE_FONTSMOOTHINGORIENTATIONRGB :: 0x0001 + +SPI_GETMINIMUMHITRADIUS :: 0x2014 +SPI_SETMINIMUMHITRADIUS :: 0x2015 +SPI_GETMESSAGEDURATION :: 0x2016 +SPI_SETMESSAGEDURATION :: 0x2017 + +SPI_GETCONTACTVISUALIZATION :: 0x2018 +SPI_SETCONTACTVISUALIZATION :: 0x2019 +// constants for SPI_GETCONTACTVISUALIZATION and SPI_SETCONTACTVISUALIZATION +CONTACTVISUALIZATION_OFF :: 0x0000 +CONTACTVISUALIZATION_ON :: 0x0001 +CONTACTVISUALIZATION_PRESENTATIONMODE :: 0x0002 + +SPI_GETGESTUREVISUALIZATION :: 0x201A +SPI_SETGESTUREVISUALIZATION :: 0x201B +// constants for SPI_GETGESTUREVISUALIZATION and SPI_SETGESTUREVISUALIZATION +GESTUREVISUALIZATION_OFF :: 0x0000 +GESTUREVISUALIZATION_ON :: 0x001F +GESTUREVISUALIZATION_TAP :: 0x0001 +GESTUREVISUALIZATION_DOUBLETAP :: 0x0002 +GESTUREVISUALIZATION_PRESSANDTAP :: 0x0004 +GESTUREVISUALIZATION_PRESSANDHOLD :: 0x0008 +GESTUREVISUALIZATION_RIGHTTAP :: 0x0010 + +SPI_GETMOUSEWHEELROUTING :: 0x201C +SPI_SETMOUSEWHEELROUTING :: 0x201D + +MOUSEWHEEL_ROUTING_FOCUS :: 0 +MOUSEWHEEL_ROUTING_HYBRID :: 1 +MOUSEWHEEL_ROUTING_MOUSE_POS :: 2 + +// Flags +SPIF_UPDATEINIFILE :: 0x0001 +SPIF_SENDWININICHANGE :: 0x0002 +SPIF_SENDCHANGE :: SPIF_SENDWININICHANGE diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index c3e461ba0..d06cd287c 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -55,7 +55,7 @@ UINT_PTR :: uintptr ULONG :: c_ulong UCHAR :: BYTE NTSTATUS :: c.long -COLORREF :: DWORD // Windows colors are packed as ABGR +COLORREF :: DWORD LPCOLORREF :: ^COLORREF LPARAM :: LONG_PTR WPARAM :: UINT_PTR @@ -164,6 +164,21 @@ FILE_GENERIC_ALL: DWORD : 0x10000000 FILE_GENERIC_EXECUTE: DWORD : 0x20000000 FILE_GENERIC_READ: DWORD : 0x80000000 +FILE_ACTION_ADDED :: 0x00000001 +FILE_ACTION_REMOVED :: 0x00000002 +FILE_ACTION_MODIFIED :: 0x00000003 +FILE_ACTION_RENAMED_OLD_NAME :: 0x00000004 +FILE_ACTION_RENAMED_NEW_NAME :: 0x00000005 + +FILE_NOTIFY_CHANGE_FILE_NAME :: 0x00000001 +FILE_NOTIFY_CHANGE_DIR_NAME :: 0x00000002 +FILE_NOTIFY_CHANGE_ATTRIBUTES :: 0x00000004 +FILE_NOTIFY_CHANGE_SIZE :: 0x00000008 +FILE_NOTIFY_CHANGE_LAST_WRITE :: 0x00000010 +FILE_NOTIFY_CHANGE_LAST_ACCESS :: 0x00000020 +FILE_NOTIFY_CHANGE_CREATION :: 0x00000040 +FILE_NOTIFY_CHANGE_SECURITY :: 0x00000100 + CREATE_NEW: DWORD : 1 CREATE_ALWAYS: DWORD : 2 OPEN_ALWAYS: DWORD : 4 @@ -1365,6 +1380,13 @@ FILE_END_OF_FILE_INFO :: struct { EndOfFile: LARGE_INTEGER, } +FILE_NOTIFY_INFORMATION :: struct { + next_entry_offset: DWORD, + action: DWORD, + file_name_length: DWORD, + file_name: [1]WCHAR, +} + REPARSE_DATA_BUFFER :: struct { ReparseTag: c_uint, ReparseDataLength: c_ushort, @@ -1509,6 +1531,12 @@ OVERLAPPED :: struct { hEvent: HANDLE, } +LPOVERLAPPED_COMPLETION_ROUTINE :: #type proc "stdcall" ( + dwErrorCode: DWORD, + dwNumberOfBytesTransfered: DWORD, + lpOverlapped: LPOVERLAPPED, +) + ADDRESS_MODE :: enum c_int { AddrMode1616, AddrMode1632, diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index dd45df42a..2010f0810 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -105,6 +105,7 @@ foreign user32 { GetWindowRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- GetClientRect :: proc(hWnd: HWND, lpRect: LPRECT) -> BOOL --- ClientToScreen :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- + ScreenToClient :: proc(hWnd: HWND, lpPoint: LPPOINT) -> BOOL --- SetWindowPos :: proc( hWnd: HWND, hWndInsertAfter: HWND, @@ -114,10 +115,13 @@ foreign user32 { cy: c_int, uFlags: UINT, ) -> BOOL --- + MoveWindow :: proc(hWnd: HWND, X, Y, hWidth, hHeight: c_int, bRepaint: BOOL) -> BOOL --- GetSystemMetrics :: proc(nIndex: c_int) -> c_int --- AdjustWindowRect :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL) -> BOOL --- AdjustWindowRectEx :: proc(lpRect: LPRECT, dwStyle: DWORD, bMenu: BOOL, dwExStyle: DWORD) -> BOOL --- + SystemParametersInfoW :: proc(uiAction, uiParam: UINT, pvParam: PVOID, fWinIni: UINT) -> BOOL --- + GetWindowDC :: proc(hWnd: HWND) -> HDC --- GetDC :: proc(hWnd: HWND) -> HDC --- ReleaseDC :: proc(hWnd: HWND, hDC: HDC) -> c_int --- @@ -152,6 +156,11 @@ foreign user32 { MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> c_int --- MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int --- MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> c_int --- + + ClipCursor :: proc(lpRect: LPRECT) -> BOOL --- + GetCursorPos :: proc(lpPoint: LPPOINT) -> BOOL --- + SetCursorPos :: proc(X: c_int, Y: c_int) -> BOOL --- + SetCursor :: proc(hCursor: HCURSOR) -> HCURSOR --- } CreateWindowA :: #force_inline proc "stdcall" ( diff --git a/core/time/time_js.odin b/core/time/time_js.odin index 1b801cdad..cfe54b86b 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -19,3 +19,5 @@ _tick_now :: proc "contextless" () -> Tick { return {} } +_yield :: proc "contextless" () { +} diff --git a/examples/all/all_vendor.odin b/examples/all/all_vendor.odin index f83e906a2..7da2e501b 100644 --- a/examples/all/all_vendor.odin +++ b/examples/all/all_vendor.odin @@ -2,12 +2,14 @@ package all import botan "vendor:botan" import ENet "vendor:ENet" +import ggpo "vendor:ggpo" import gl "vendor:OpenGL" import glfw "vendor:glfw" import microui "vendor:microui" import miniaudio "vendor:miniaudio" import PM "vendor:portmidi" import rl "vendor:raylib" +import exr "vendor:OpenEXRCore" import SDL "vendor:sdl2" import SDLNet "vendor:sdl2/net" @@ -23,12 +25,14 @@ import CA "vendor:darwin/QuartzCore" _ :: botan _ :: ENet +_ :: ggpo _ :: gl _ :: glfw _ :: microui _ :: miniaudio _ :: PM _ :: rl +_ :: exr _ :: SDL _ :: SDLNet _ :: IMG diff --git a/src/big_int.cpp b/src/big_int.cpp index 20f940e8e..5509545ca 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -40,7 +40,7 @@ typedef mp_int BigInt; void big_int_from_u64(BigInt *dst, u64 x); void big_int_from_i64(BigInt *dst, i64 x); void big_int_init (BigInt *dst, BigInt const *src); -void big_int_from_string(BigInt *dst, String const &s); +void big_int_from_string(BigInt *dst, String const &s, bool *success); void big_int_dealloc(BigInt *dst) { mp_clear(dst); @@ -84,7 +84,7 @@ void big_int_quo_eq(BigInt *dst, BigInt const *x); void big_int_rem_eq(BigInt *dst, BigInt const *x); bool big_int_is_neg(BigInt const *x); - +void big_int_neg(BigInt *dst, BigInt const *x); void big_int_add_eq(BigInt *dst, BigInt const *x) { BigInt res = {}; @@ -169,7 +169,11 @@ BigInt big_int_make_i64(i64 x) { } -void big_int_from_string(BigInt *dst, String const &s) { +void big_int_from_string(BigInt *dst, String const &s, bool *success) { + *success = true; + + bool is_negative = false; + u64 base = 10; bool has_prefix = false; if (s.len > 2 && s[0] == '0') { @@ -197,11 +201,26 @@ void big_int_from_string(BigInt *dst, String const &s) { isize i = 0; for (; i < len; i++) { Rune r = cast(Rune)text[i]; + + if (r == '-') { + if (is_negative) { + // NOTE(Jeroen): Can't have a doubly negative number. + *success = false; + return; + } + is_negative = true; + continue; + } + if (r == '_') { continue; } u64 v = u64_digit_value(r); if (v >= base) { + // NOTE(Jeroen): Can still be a valid integer if the next character is an `e` or `E`. + if (r != 'e' && r != 'E') { + *success = false; + } break; } BigInt val = big_int_make_u64(v); @@ -225,6 +244,7 @@ void big_int_from_string(BigInt *dst, String const &s) { if (gb_char_is_digit(r)) { v = u64_digit_value(r); } else { + *success = false; break; } exp *= 10; @@ -234,6 +254,10 @@ void big_int_from_string(BigInt *dst, String const &s) { big_int_mul_eq(dst, &b); } } + + if (is_negative) { + big_int_neg(dst, dst); + } } diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 5acd56097..82ac6c677 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1018,18 +1018,16 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } Entity *foreign_library = init_entity_foreign_library(ctx, e); - if (is_arch_wasm()) { + if (is_arch_wasm() && foreign_library != nullptr) { String module_name = str_lit("env"); - if (foreign_library != nullptr) { - GB_ASSERT (foreign_library->kind == Entity_LibraryName); - if (foreign_library->LibraryName.paths.count != 1) { - error(foreign_library->token, "'foreign import' for '%.*s' architecture may only have one path, got %td", - LIT(target_arch_names[build_context.metrics.arch]), foreign_library->LibraryName.paths.count); - } - - if (foreign_library->LibraryName.paths.count >= 1) { - module_name = foreign_library->LibraryName.paths[0]; - } + GB_ASSERT (foreign_library->kind == Entity_LibraryName); + if (foreign_library->LibraryName.paths.count != 1) { + error(foreign_library->token, "'foreign import' for '%.*s' architecture may only have one path, got %td", + LIT(target_arch_names[build_context.metrics.arch]), foreign_library->LibraryName.paths.count); + } + + if (foreign_library->LibraryName.paths.count >= 1) { + module_name = foreign_library->LibraryName.paths[0]; } name = concatenate3_strings(permanent_allocator(), module_name, WASM_MODULE_NAME_SEPARATOR, name); } diff --git a/src/checker.cpp b/src/checker.cpp index 1e33c6e9d..fdd75126f 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4414,6 +4414,14 @@ DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) { } ac->require_declaration = true; return true; + } else if (name == "priority_index") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind != ExactValue_Integer) { + error(elem, "Expected an integer value for '%.*s'", LIT(name)); + } else { + ac->foreign_import_priority_index = exact_value_to_i64(ev); + } + return true; } return false; } @@ -4470,6 +4478,9 @@ void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { mpmc_enqueue(&ctx->info->required_foreign_imports_through_force_queue, e); add_entity_use(ctx, nullptr, e); } + if (ac.foreign_import_priority_index != 0) { + e->LibraryName.priority_index = ac.foreign_import_priority_index; + } if (has_asm_extension(fullpath)) { if (build_context.metrics.arch != TargetArch_amd64 || diff --git a/src/checker.hpp b/src/checker.hpp index 552e6aca7..1c9ffd8c7 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -118,6 +118,7 @@ struct AttributeContext { bool init : 1; bool set_cold : 1; u32 optimization_mode; // ProcedureOptimizationMode + i64 foreign_import_priority_index; String objc_class; String objc_name; diff --git a/src/entity.cpp b/src/entity.cpp index 1f87f7af6..904a630fb 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -252,6 +252,7 @@ struct Entity { struct { Slice paths; String name; + i64 priority_index; } LibraryName; i32 Nil; struct { diff --git a/src/exact_value.cpp b/src/exact_value.cpp index cedef48c4..175cb61f6 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -177,7 +177,11 @@ ExactValue exact_value_typeid(Type *type) { ExactValue exact_value_integer_from_string(String const &string) { ExactValue result = {ExactValue_Integer}; - big_int_from_string(&result.value_integer, string); + bool success; + big_int_from_string(&result.value_integer, string, &success); + if (!success) { + result = {ExactValue_Invalid}; + } return result; } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 7781997f7..7cf588853 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -29,29 +29,53 @@ void lb_add_foreign_library_path(lbModule *m, Entity *e) { GB_ASSERT(e->kind == Entity_LibraryName); GB_ASSERT(e->flags & EntityFlag_Used); - for_array(i, e->LibraryName.paths) { - String library_path = e->LibraryName.paths[i]; - if (library_path.len == 0) { - continue; - } + mutex_lock(&m->gen->foreign_mutex); + if (!ptr_set_update(&m->gen->foreign_libraries_set, e)) { + array_add(&m->gen->foreign_libraries, e); + } + mutex_unlock(&m->gen->foreign_mutex); +} - bool ok = true; - for_array(path_index, m->foreign_library_paths) { - String path = m->foreign_library_paths[path_index]; - #if defined(GB_SYSTEM_WINDOWS) - if (str_eq_ignore_case(path, library_path)) { - #else - if (str_eq(path, library_path)) { - #endif - ok = false; - break; - } - } +GB_COMPARE_PROC(foreign_library_cmp) { + int cmp = 0; + Entity *x = *(Entity **)a; + Entity *y = *(Entity **)b; + if (x == y) { + return 0; + } + GB_ASSERT(x->kind == Entity_LibraryName); + GB_ASSERT(y->kind == Entity_LibraryName); - if (ok) { - array_add(&m->foreign_library_paths, library_path); + cmp = i64_cmp(x->LibraryName.priority_index, y->LibraryName.priority_index); + if (cmp) { + return cmp; + } + + if (x->pkg != y->pkg) { + isize order_x = x->pkg ? x->pkg->order : 0; + isize order_y = y->pkg ? y->pkg->order : 0; + cmp = isize_cmp(order_x, order_y); + if (cmp) { + return cmp; } } + if (x->file != y->file) { + String fullpath_x = x->file ? x->file->fullpath : (String{}); + String fullpath_y = y->file ? y->file->fullpath : (String{}); + String file_x = filename_from_path(fullpath_x); + String file_y = filename_from_path(fullpath_y); + + cmp = string_compare(file_x, file_y); + if (cmp) { + return cmp; + } + } + + cmp = u64_cmp(x->order_in_src, y->order_in_src); + if (cmp) { + return cmp; + } + return i32_cmp(x->token.pos.offset, y->token.pos.offset); } void lb_set_entity_from_other_modules_linkage_correctly(lbModule *other_module, Entity *e, String const &name) { @@ -1922,4 +1946,6 @@ void lb_generate_code(lbGenerator *gen) { } } } + + gb_sort_array(gen->foreign_libraries.data, gen->foreign_libraries.count, foreign_library_cmp); } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index f2bcfaff6..a460b1a23 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -135,7 +135,6 @@ struct lbModule { u32 nested_type_name_guid; Array procedures_to_generate; - Array foreign_library_paths; lbProcedure *curr_procedure; @@ -162,6 +161,10 @@ struct lbGenerator { PtrMap anonymous_proc_lits; + BlockingMutex foreign_mutex; + PtrSet foreign_libraries_set; + Array foreign_libraries; + std::atomic global_array_index; std::atomic global_generated_index; }; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 1a431a4ac..0866e3687 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -67,9 +67,7 @@ void lb_init_module(lbModule *m, Checker *c) { map_init(&m->equal_procs, a); map_init(&m->hasher_procs, a); array_init(&m->procedures_to_generate, a, 0, 1024); - array_init(&m->foreign_library_paths, a, 0, 1024); array_init(&m->missing_procedures_to_check, a, 0, 16); - map_init(&m->debug_values, a); array_init(&m->debug_incomplete_types, a, 0, 1024); @@ -126,6 +124,11 @@ bool lb_init_generator(lbGenerator *gen, Checker *c) { map_init(&gen->modules_through_ctx, permanent_allocator(), gen->info->packages.entries.count*2); map_init(&gen->anonymous_proc_lits, heap_allocator(), 1024); + + mutex_init(&gen->foreign_mutex); + array_init(&gen->foreign_libraries, heap_allocator(), 0, 1024); + ptr_set_init(&gen->foreign_libraries_set, heap_allocator(), 1024); + if (USE_SEPARATE_MODULES) { for_array(i, gen->info->packages.entries) { AstPackage *pkg = gen->info->packages.entries[i].value; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 06a74f625..a0e9a5da5 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -823,12 +823,6 @@ lbValue lb_emit_call(lbProcedure *p, lbValue value, Array const &args, GB_ASSERT(pt->kind == Type_Proc); Type *results = pt->Proc.results; - if (p->entity != nullptr) { - if (p->entity->flags & EntityFlag_Disabled) { - return {}; - } - } - lbAddr context_ptr = {}; if (pt->Proc.calling_convention == ProcCC_Odin) { context_ptr = lb_find_or_generate_context_ptr(p); @@ -2280,6 +2274,15 @@ lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { // NOTE(bill): Regular call lbValue value = {}; Ast *proc_expr = unparen_expr(ce->proc); + + Entity *proc_entity = entity_of_node(proc_expr); + if (proc_entity != nullptr) { + if (proc_entity->flags & EntityFlag_Disabled) { + GB_ASSERT(tv.type == nullptr); + return {}; + } + } + if (proc_expr->tav.mode == Addressing_Constant) { ExactValue v = proc_expr->tav.value; switch (v.kind) { @@ -2306,13 +2309,6 @@ lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } } - Entity *proc_entity = entity_of_node(proc_expr); - if (proc_entity != nullptr) { - if (proc_entity->flags & EntityFlag_Disabled) { - return {}; - } - } - if (value.value == nullptr) { value = lb_build_expr(p, proc_expr); } diff --git a/src/main.cpp b/src/main.cpp index 818a783e1..561fa0fca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -164,341 +164,360 @@ i32 linker_stage(lbGenerator *gen) { build_context.keep_object_files = true; } else { #if defined(GB_SYSTEM_WINDOWS) - String section_name = str_lit("msvc-link"); - if (build_context.use_lld) { - section_name = str_lit("lld-link"); - } - timings_start_section(timings, section_name); - - gbString lib_str = gb_string_make(heap_allocator(), ""); - defer (gb_string_free(lib_str)); - - gbString link_settings = gb_string_make_reserve(heap_allocator(), 256); - defer (gb_string_free(link_settings)); - - // Add library search paths. - if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) { - String path = {}; - auto add_path = [&](String path) { - if (path[path.len-1] == '\\') { - path.len -= 1; - } - link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path)); - }; - add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename); - add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename); - add_path(build_context.build_paths[BuildPath_VS_LIB].basename); - } + bool is_windows = true; + #else + bool is_windows = false; + #endif + #if defined(GB_SYSTEM_OSX) + bool is_osx = true; + #else + bool is_osx = false; + #endif - StringSet libs = {}; - string_set_init(&libs, heap_allocator(), 64); - defer (string_set_destroy(&libs)); + if (is_windows) { + String section_name = str_lit("msvc-link"); + if (build_context.use_lld) { + section_name = str_lit("lld-link"); + } + timings_start_section(timings, section_name); - StringSet asm_files = {}; - string_set_init(&asm_files, heap_allocator(), 64); - defer (string_set_destroy(&asm_files)); + gbString lib_str = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(lib_str)); - for_array(j, gen->modules.entries) { - lbModule *m = gen->modules.entries[j].value; - for_array(i, m->foreign_library_paths) { - String lib = m->foreign_library_paths[i]; - if (has_asm_extension(lib)) { - string_set_add(&asm_files, lib); - } else { - string_set_add(&libs, lib); + gbString link_settings = gb_string_make_reserve(heap_allocator(), 256); + defer (gb_string_free(link_settings)); + + // Add library search paths. + if (build_context.build_paths[BuildPath_VS_LIB].basename.len > 0) { + String path = {}; + auto add_path = [&](String path) { + if (path[path.len-1] == '\\') { + path.len -= 1; + } + link_settings = gb_string_append_fmt(link_settings, " /LIBPATH:\"%.*s\"", LIT(path)); + }; + add_path(build_context.build_paths[BuildPath_Win_SDK_UM_Lib].basename); + add_path(build_context.build_paths[BuildPath_Win_SDK_UCRT_Lib].basename); + add_path(build_context.build_paths[BuildPath_VS_LIB].basename); + } + + + StringSet libs = {}; + string_set_init(&libs, heap_allocator(), 64); + defer (string_set_destroy(&libs)); + + StringSet asm_files = {}; + string_set_init(&asm_files, heap_allocator(), 64); + defer (string_set_destroy(&asm_files)); + + for_array(j, gen->foreign_libraries) { + Entity *e = gen->foreign_libraries[j]; + GB_ASSERT(e->kind == Entity_LibraryName); + for_array(i, e->LibraryName.paths) { + String lib = string_trim_whitespace(e->LibraryName.paths[i]); + // IMPORTANT NOTE(bill): calling `string_to_lower` here is not an issue because + // we will never uses these strings afterwards + string_to_lower(&lib); + if (lib.len == 0) { + continue; + } + + if (has_asm_extension(lib)) { + if (!string_set_update(&asm_files, lib)) { + String asm_file = asm_files.entries[i].value; + String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); + + result = system_exec_command_line_app("nasm", + "\"%.*s\\bin\\nasm\\windows\\nasm.exe\" \"%.*s\" " + "-f win64 " + "-o \"%.*s\" " + "%.*s " + "", + LIT(build_context.ODIN_ROOT), LIT(asm_file), + LIT(obj_file), + LIT(build_context.extra_assembler_flags) + ); + + if (result) { + return result; + } + array_add(&gen->output_object_paths, obj_file); + } + } else { + if (!string_set_update(&libs, lib)) { + lib_str = gb_string_append_fmt(lib_str, " \"%.*s\"", LIT(lib)); + } + } } } - } - for_array(i, gen->default_module.foreign_library_paths) { - String lib = gen->default_module.foreign_library_paths[i]; - if (has_asm_extension(lib)) { - string_set_add(&asm_files, lib); + if (build_context.build_mode == BuildMode_DynamicLibrary) { + link_settings = gb_string_append_fmt(link_settings, " /DLL"); } else { - string_set_add(&libs, lib); + link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup"); } - } - for_array(i, libs.entries) { - String lib = libs.entries[i].value; - lib_str = gb_string_append_fmt(lib_str, " \"%.*s\"", LIT(lib)); - } - - - if (build_context.build_mode == BuildMode_DynamicLibrary) { - link_settings = gb_string_append_fmt(link_settings, " /DLL"); - } else { - link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup"); - } - - if (build_context.pdb_filepath != "") { - String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]); - link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path)); - } - - if (build_context.no_crt) { - link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib"); - } else { - link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt"); - } - - if (build_context.ODIN_DEBUG) { - link_settings = gb_string_append_fmt(link_settings, " /DEBUG"); - } - - for_array(i, asm_files.entries) { - String asm_file = asm_files.entries[i].value; - String obj_file = concatenate_strings(permanent_allocator(), asm_file, str_lit(".obj")); - - result = system_exec_command_line_app("nasm", - "\"%.*s\\bin\\nasm\\windows\\nasm.exe\" \"%.*s\" " - "-f win64 " - "-o \"%.*s\" " - "%.*s " - "", - LIT(build_context.ODIN_ROOT), LIT(asm_file), - LIT(obj_file), - LIT(build_context.extra_assembler_flags) - ); - - if (result) { - return result; + if (build_context.pdb_filepath != "") { + String pdb_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_PDB]); + link_settings = gb_string_append_fmt(link_settings, " /PDB:%.*s", LIT(pdb_path)); } - array_add(&gen->output_object_paths, obj_file); - } - gbString object_files = gb_string_make(heap_allocator(), ""); - defer (gb_string_free(object_files)); - for_array(i, gen->output_object_paths) { - String object_path = gen->output_object_paths[i]; - object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); - } + if (build_context.no_crt) { + link_settings = gb_string_append_fmt(link_settings, " /nodefaultlib"); + } else { + link_settings = gb_string_append_fmt(link_settings, " /defaultlib:libcmt"); + } - String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]); - defer (gb_free(heap_allocator(), vs_exe_path.text)); + if (build_context.ODIN_DEBUG) { + link_settings = gb_string_append_fmt(link_settings, " /DEBUG"); + } - char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE"; - if (!build_context.use_lld) { // msvc - if (build_context.has_resource) { - String rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]); - String res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]); - defer (gb_free(heap_allocator(), rc_path.text)); - defer (gb_free(heap_allocator(), res_path.text)); + gbString object_files = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(object_files)); + for_array(i, gen->output_object_paths) { + String object_path = gen->output_object_paths[i]; + object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); + } - result = system_exec_command_line_app("msvc-link", - "\"rc.exe\" /nologo /fo \"%.*s\" \"%.*s\"", - LIT(res_path), - LIT(rc_path) - ); + String vs_exe_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_VS_EXE]); + defer (gb_free(heap_allocator(), vs_exe_path.text)); + + char const *subsystem_str = build_context.use_subsystem_windows ? "WINDOWS" : "CONSOLE"; + if (!build_context.use_lld) { // msvc + if (build_context.has_resource) { + String rc_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RC]); + String res_path = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_RES]); + defer (gb_free(heap_allocator(), rc_path.text)); + defer (gb_free(heap_allocator(), res_path.text)); + + result = system_exec_command_line_app("msvc-link", + "\"rc.exe\" /nologo /fo \"%.*s\" \"%.*s\"", + LIT(res_path), + LIT(rc_path) + ); + + if (result) { + return result; + } + + result = system_exec_command_line_app("msvc-link", + "\"%.*slink.exe\" %s \"%.*s\" -OUT:\"%.*s\" %s " + "/nologo /incremental:no /opt:ref /subsystem:%s " + " %.*s " + " %.*s " + " %s " + "", + LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename), + link_settings, + subsystem_str, + LIT(build_context.link_flags), + LIT(build_context.extra_linker_flags), + lib_str + ); + } else { + result = system_exec_command_line_app("msvc-link", + "\"%.*slink.exe\" %s -OUT:\"%.*s\" %s " + "/nologo /incremental:no /opt:ref /subsystem:%s " + " %.*s " + " %.*s " + " %s " + "", + LIT(vs_exe_path), object_files, LIT(output_filename), + link_settings, + subsystem_str, + LIT(build_context.link_flags), + LIT(build_context.extra_linker_flags), + lib_str + ); + } if (result) { return result; } - result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s \"%.*s\" -OUT:\"%.*s\" %s " + } else { // lld + result = system_exec_command_line_app("msvc-lld-link", + "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " " %.*s " " %.*s " " %s " "", - LIT(vs_exe_path), object_files, LIT(res_path), LIT(output_filename), - link_settings, - subsystem_str, - LIT(build_context.link_flags), - LIT(build_context.extra_linker_flags), - lib_str - ); - } else { - result = system_exec_command_line_app("msvc-link", - "\"%.*slink.exe\" %s -OUT:\"%.*s\" %s " - "/nologo /incremental:no /opt:ref /subsystem:%s " - " %.*s " - " %.*s " - " %s " - "", - LIT(vs_exe_path), object_files, LIT(output_filename), + LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), link_settings, subsystem_str, LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), lib_str ); - } - if (result) { - return result; - } - - } else { // lld - result = system_exec_command_line_app("msvc-lld-link", - "\"%.*s\\bin\\lld-link\" %s -OUT:\"%.*s\" %s " - "/nologo /incremental:no /opt:ref /subsystem:%s " - " %.*s " - " %.*s " - " %s " - "", - LIT(build_context.ODIN_ROOT), object_files, LIT(output_filename), - link_settings, - subsystem_str, - LIT(build_context.link_flags), - LIT(build_context.extra_linker_flags), - lib_str - ); - - if (result) { - return result; - } - } - #else - timings_start_section(timings, str_lit("ld-link")); - - // NOTE(vassvik): get cwd, for used for local shared libs linking, since those have to be relative to the exe - char cwd[256]; - getcwd(&cwd[0], 256); - //printf("%s\n", cwd); - - // NOTE(vassvik): needs to add the root to the library search paths, so that the full filenames of the library - // files can be passed with -l: - gbString lib_str = gb_string_make(heap_allocator(), "-L/"); - defer (gb_string_free(lib_str)); - - for_array(i, gen->default_module.foreign_library_paths) { - String lib = gen->default_module.foreign_library_paths[i]; - - // NOTE(zangent): Sometimes, you have to use -framework on MacOS. - // This allows you to specify '-f' in a #foreign_system_library, - // without having to implement any new syntax specifically for MacOS. - if (build_context.metrics.os == TargetOs_darwin) { - if (string_ends_with(lib, str_lit(".framework"))) { - // framework thingie - String lib_name = lib; - lib_name = remove_extension_from_path(lib_name); - lib_str = gb_string_append_fmt(lib_str, " -framework %.*s ", LIT(lib_name)); - } else if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o")) || string_ends_with(lib, str_lit(".dylib"))) { - // For: - // object - // dynamic lib - // static libs, absolute full path relative to the file in which the lib was imported from - lib_str = gb_string_append_fmt(lib_str, " %.*s ", LIT(lib)); - } else { - // dynamic or static system lib, just link regularly searching system library paths - lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); - } - } else { - // NOTE(vassvik): static libraries (.a files) in linux can be linked to directly using the full path, - // since those are statically linked to at link time. shared libraries (.so) has to be - // available at runtime wherever the executable is run, so we make require those to be - // local to the executable (unless the system collection is used, in which case we search - // the system library paths for the library file). - if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) { - // static libs and object files, absolute full path relative to the file in which the lib was imported from - lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib)); - } else if (string_ends_with(lib, str_lit(".so"))) { - // dynamic lib, relative path to executable - // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible - // at runtime to the executable - lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib)); - } else { - // dynamic or static system lib, just link regularly searching system library paths - lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); + if (result) { + return result; } } - } - - gbString object_files = gb_string_make(heap_allocator(), ""); - defer (gb_string_free(object_files)); - for_array(i, gen->output_object_paths) { - String object_path = gen->output_object_paths[i]; - object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); - } - - gbString link_settings = gb_string_make_reserve(heap_allocator(), 32); - - if (build_context.no_crt) { - link_settings = gb_string_append_fmt(link_settings, "-nostdlib "); - } - - // NOTE(dweiler): We use clang as a frontend for the linker as there are - // other runtime and compiler support libraries that need to be linked in - // very specific orders such as libgcc_s, ld-linux-so, unwind, etc. - // These are not always typically inside /lib, /lib64, or /usr versions - // of that, e.g libgcc.a is in /usr/lib/gcc/{version}, and can vary on - // the distribution of Linux even. The gcc or clang specs is the only - // reliable way to query this information to call ld directly. - if (build_context.build_mode == BuildMode_DynamicLibrary) { - // NOTE(dweiler): Let the frontend know we're building a shared library - // so it doesn't generate symbols which cannot be relocated. - link_settings = gb_string_appendc(link_settings, "-shared "); - - // NOTE(dweiler): _odin_entry_point must be called at initialization - // time of the shared object, similarly, _odin_exit_point must be called - // at deinitialization. We can pass both -init and -fini to the linker by - // using a comma separated list of arguments to -Wl. - // - // This previously used ld but ld cannot actually build a shared library - // correctly this way since all the other dependencies provided implicitly - // by the compiler frontend are still needed and most of the command - // line arguments prepared previously are incompatible with ld. - link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' "); - link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); - } else if (build_context.metrics.os != TargetOs_openbsd) { - // OpenBSD defaults to PIE executable. do not pass -no-pie for it. - link_settings = gb_string_appendc(link_settings, "-no-pie "); - } - - gbString platform_lib_str = gb_string_make(heap_allocator(), ""); - defer (gb_string_free(platform_lib_str)); - if (build_context.metrics.os == TargetOs_darwin) { - platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem -lm -Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib"); } else { - platform_lib_str = gb_string_appendc(platform_lib_str, "-lc -lm"); - } + timings_start_section(timings, str_lit("ld-link")); - if (build_context.metrics.os == TargetOs_darwin) { - // This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit. - // NOTE: If you change this (although this minimum is as low as you can go with Odin working) - // make sure to also change the 'mtriple' param passed to 'opt' - if (build_context.metrics.arch == TargetArch_arm64) { - link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0 "); - } else { - link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.8.0 "); + // NOTE(vassvik): get cwd, for used for local shared libs linking, since those have to be relative to the exe + char cwd[256]; + #if !defined(GB_SYSTEM_WINDOWS) + getcwd(&cwd[0], 256); + #endif + //printf("%s\n", cwd); + + // NOTE(vassvik): needs to add the root to the library search paths, so that the full filenames of the library + // files can be passed with -l: + gbString lib_str = gb_string_make(heap_allocator(), "-L/"); + defer (gb_string_free(lib_str)); + + StringSet libs = {}; + string_set_init(&libs, heap_allocator(), 64); + defer (string_set_destroy(&libs)); + + for_array(j, gen->foreign_libraries) { + Entity *e = gen->foreign_libraries[j]; + GB_ASSERT(e->kind == Entity_LibraryName); + for_array(i, e->LibraryName.paths) { + String lib = string_trim_whitespace(e->LibraryName.paths[i]); + if (lib.len == 0) { + continue; + } + if (string_set_update(&libs, lib)) { + continue; + } + + // NOTE(zangent): Sometimes, you have to use -framework on MacOS. + // This allows you to specify '-f' in a #foreign_system_library, + // without having to implement any new syntax specifically for MacOS. + if (build_context.metrics.os == TargetOs_darwin) { + if (string_ends_with(lib, str_lit(".framework"))) { + // framework thingie + String lib_name = lib; + lib_name = remove_extension_from_path(lib_name); + lib_str = gb_string_append_fmt(lib_str, " -framework %.*s ", LIT(lib_name)); + } else if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o")) || string_ends_with(lib, str_lit(".dylib"))) { + // For: + // object + // dynamic lib + // static libs, absolute full path relative to the file in which the lib was imported from + lib_str = gb_string_append_fmt(lib_str, " %.*s ", LIT(lib)); + } else { + // dynamic or static system lib, just link regularly searching system library paths + lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); + } + } else { + // NOTE(vassvik): static libraries (.a files) in linux can be linked to directly using the full path, + // since those are statically linked to at link time. shared libraries (.so) has to be + // available at runtime wherever the executable is run, so we make require those to be + // local to the executable (unless the system collection is used, in which case we search + // the system library paths for the library file). + if (string_ends_with(lib, str_lit(".a")) || string_ends_with(lib, str_lit(".o"))) { + // static libs and object files, absolute full path relative to the file in which the lib was imported from + lib_str = gb_string_append_fmt(lib_str, " -l:\"%.*s\" ", LIT(lib)); + } else if (string_ends_with(lib, str_lit(".so"))) { + // dynamic lib, relative path to executable + // NOTE(vassvik): it is the user's responsibility to make sure the shared library files are visible + // at runtime to the executable + lib_str = gb_string_append_fmt(lib_str, " -l:\"%s/%.*s\" ", cwd, LIT(lib)); + } else { + // dynamic or static system lib, just link regularly searching system library paths + lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib)); + } + } + } } - // This points the linker to where the entry point is - link_settings = gb_string_appendc(link_settings, " -e _main "); - } - gbString link_command_line = gb_string_make(heap_allocator(), "clang -Wno-unused-command-line-argument "); - defer (gb_string_free(link_command_line)); - link_command_line = gb_string_appendc(link_command_line, object_files); - link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename)); - link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str); - link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str); - link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags)); - link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.extra_linker_flags)); - link_command_line = gb_string_append_fmt(link_command_line, " %s ", link_settings); + gbString object_files = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(object_files)); + for_array(i, gen->output_object_paths) { + String object_path = gen->output_object_paths[i]; + object_files = gb_string_append_fmt(object_files, "\"%.*s\" ", LIT(object_path)); + } - result = system_exec_command_line_app("ld-link", link_command_line); + gbString link_settings = gb_string_make_reserve(heap_allocator(), 32); - if (result) { - return result; - } + if (build_context.no_crt) { + link_settings = gb_string_append_fmt(link_settings, "-nostdlib "); + } - #if defined(GB_SYSTEM_OSX) - if (build_context.ODIN_DEBUG) { - // NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe - // to the symbols in the object file - result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename)); + // NOTE(dweiler): We use clang as a frontend for the linker as there are + // other runtime and compiler support libraries that need to be linked in + // very specific orders such as libgcc_s, ld-linux-so, unwind, etc. + // These are not always typically inside /lib, /lib64, or /usr versions + // of that, e.g libgcc.a is in /usr/lib/gcc/{version}, and can vary on + // the distribution of Linux even. The gcc or clang specs is the only + // reliable way to query this information to call ld directly. + if (build_context.build_mode == BuildMode_DynamicLibrary) { + // NOTE(dweiler): Let the frontend know we're building a shared library + // so it doesn't generate symbols which cannot be relocated. + link_settings = gb_string_appendc(link_settings, "-shared "); + + // NOTE(dweiler): _odin_entry_point must be called at initialization + // time of the shared object, similarly, _odin_exit_point must be called + // at deinitialization. We can pass both -init and -fini to the linker by + // using a comma separated list of arguments to -Wl. + // + // This previously used ld but ld cannot actually build a shared library + // correctly this way since all the other dependencies provided implicitly + // by the compiler frontend are still needed and most of the command + // line arguments prepared previously are incompatible with ld. + link_settings = gb_string_appendc(link_settings, "-Wl,-init,'_odin_entry_point' "); + link_settings = gb_string_appendc(link_settings, "-Wl,-fini,'_odin_exit_point' "); + } else if (build_context.metrics.os != TargetOs_openbsd) { + // OpenBSD defaults to PIE executable. do not pass -no-pie for it. + link_settings = gb_string_appendc(link_settings, "-no-pie "); + } + + gbString platform_lib_str = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(platform_lib_str)); + if (build_context.metrics.os == TargetOs_darwin) { + platform_lib_str = gb_string_appendc(platform_lib_str, "-lSystem -lm -Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib"); + } else { + platform_lib_str = gb_string_appendc(platform_lib_str, "-lc -lm"); + } + + if (build_context.metrics.os == TargetOs_darwin) { + // This sets a requirement of Mountain Lion and up, but the compiler doesn't work without this limit. + // NOTE: If you change this (although this minimum is as low as you can go with Odin working) + // make sure to also change the 'mtriple' param passed to 'opt' + if (build_context.metrics.arch == TargetArch_arm64) { + link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0 "); + } else { + link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.8.0 "); + } + // This points the linker to where the entry point is + link_settings = gb_string_appendc(link_settings, " -e _main "); + } + + gbString link_command_line = gb_string_make(heap_allocator(), "clang -Wno-unused-command-line-argument "); + defer (gb_string_free(link_command_line)); + + link_command_line = gb_string_appendc(link_command_line, object_files); + link_command_line = gb_string_append_fmt(link_command_line, " -o \"%.*s\" ", LIT(output_filename)); + link_command_line = gb_string_append_fmt(link_command_line, " %s ", platform_lib_str); + link_command_line = gb_string_append_fmt(link_command_line, " %s ", lib_str); + link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.link_flags)); + link_command_line = gb_string_append_fmt(link_command_line, " %.*s ", LIT(build_context.extra_linker_flags)); + link_command_line = gb_string_append_fmt(link_command_line, " %s ", link_settings); + + result = system_exec_command_line_app("ld-link", link_command_line); if (result) { return result; } - } - #endif - #endif + if (is_osx && build_context.ODIN_DEBUG) { + // NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe + // to the symbols in the object file + result = system_exec_command_line_app("dsymutil", "dsymutil %.*s", LIT(output_filename)); + + if (result) { + return result; + } + } + } } return result; @@ -825,11 +844,19 @@ bool parse_build_flags(Array args) { String name = substring(flag, 1, flag.len); isize end = 0; + bool have_equals = false; for (; end < name.len; end++) { if (name[end] == ':') break; - if (name[end] == '=') break; // IMPORTANT TODO(bill): DEPRECATE THIS!!!! + if (name[end] == '=') { + have_equals = true; + break; + } } name = substring(name, 0, end); + if (have_equals && name != "opt") { + gb_printf_err("`flag=value` has been deprecated and will be removed next release. Use `%.*s:` instead.\n", LIT(name)); + } + String param = {}; if (end < flag.len-1) param = substring(flag, 2+end, flag.len); @@ -903,35 +930,35 @@ bool parse_build_flags(Array args) { switch (bf.param_kind) { case BuildFlagParam_None: if (value.kind != ExactValue_Invalid) { - gb_printf_err("%.*s expected no value, got %.*s", LIT(name), LIT(param)); + gb_printf_err("%.*s expected no value, got %.*s\n", LIT(name), LIT(param)); bad_flags = true; ok = false; } break; case BuildFlagParam_Boolean: if (value.kind != ExactValue_Bool) { - gb_printf_err("%.*s expected a boolean, got %.*s", LIT(name), LIT(param)); + gb_printf_err("%.*s expected a boolean, got %.*s\n", LIT(name), LIT(param)); bad_flags = true; ok = false; } break; case BuildFlagParam_Integer: if (value.kind != ExactValue_Integer) { - gb_printf_err("%.*s expected an integer, got %.*s", LIT(name), LIT(param)); + gb_printf_err("%.*s expected an integer, got %.*s\n", LIT(name), LIT(param)); bad_flags = true; ok = false; } break; case BuildFlagParam_Float: if (value.kind != ExactValue_Float) { - gb_printf_err("%.*s expected a floating pointer number, got %.*s", LIT(name), LIT(param)); + gb_printf_err("%.*s expected a floating pointer number, got %.*s\n", LIT(name), LIT(param)); bad_flags = true; ok = false; } break; case BuildFlagParam_String: if (value.kind != ExactValue_String) { - gb_printf_err("%.*s expected a string, got %.*s", LIT(name), LIT(param)); + gb_printf_err("%.*s expected a string, got %.*s\n", LIT(name), LIT(param)); bad_flags = true; ok = false; } @@ -961,7 +988,20 @@ bool parse_build_flags(Array args) { bad_flags = true; break; } + build_context.optimization_level = cast(i32)big_int_to_i64(&value.value_integer); + if (build_context.optimization_level < 0 || build_context.optimization_level > 3) { + gb_printf_err("Invalid optimization level for -o:, got %d\n", build_context.optimization_level); + gb_printf_err("Valid optimization levels:\n"); + gb_printf_err("\t0\n"); + gb_printf_err("\t1\n"); + gb_printf_err("\t2\n"); + gb_printf_err("\t3\n"); + bad_flags = true; + } + + // Deprecation warning. + gb_printf_err("`-opt` has been deprecated and will be removed next release. Use `-o:minimal`, etc.\n"); break; } case BuildFlag_OptimizationMode: { @@ -2823,11 +2863,7 @@ int main(int arg_count, char const **arg_ptr) { String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); defer (gb_free(heap_allocator(), exe_name.text)); - #if defined(GB_SYSTEM_WINDOWS) - return system_exec_command_line_app("odin run", "%.*s %.*s", LIT(exe_name), LIT(run_args_string)); - #else return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); - #endif } return 0; } diff --git a/src/ptr_set.cpp b/src/ptr_set.cpp index b45997916..ffe48d69a 100644 --- a/src/ptr_set.cpp +++ b/src/ptr_set.cpp @@ -13,7 +13,7 @@ struct PtrSet { template void ptr_set_init (PtrSet *s, gbAllocator a, isize capacity = 16); template void ptr_set_destroy(PtrSet *s); template T ptr_set_add (PtrSet *s, T ptr); -template bool ptr_set_update (PtrSet *s, T ptr); // returns true if it previously existsed +template bool ptr_set_update (PtrSet *s, T ptr); // returns true if it previously existed template bool ptr_set_exists (PtrSet *s, T ptr); template void ptr_set_remove (PtrSet *s, T ptr); template void ptr_set_clear (PtrSet *s); diff --git a/src/string_set.cpp b/src/string_set.cpp index e27145289..746ad9529 100644 --- a/src/string_set.cpp +++ b/src/string_set.cpp @@ -13,6 +13,7 @@ struct StringSet { void string_set_init (StringSet *s, gbAllocator a, isize capacity = 16); void string_set_destroy(StringSet *s); void string_set_add (StringSet *s, String const &str); +bool string_set_update (StringSet *s, String const &str); // returns true if it previously existed bool string_set_exists (StringSet *s, String const &str); void string_set_remove (StringSet *s, String const &str); void string_set_clear (StringSet *s); @@ -149,6 +150,34 @@ void string_set_add(StringSet *s, String const &str) { } } +bool string_set_update(StringSet *s, String const &str) { + bool exists = false; + MapIndex index; + MapFindResult fr; + StringHashKey key = string_hash_string(str); + if (s->hashes.count == 0) { + string_set_grow(s); + } + fr = string_set__find(s, key); + if (fr.entry_index != MAP_SENTINEL) { + index = fr.entry_index; + exists = true; + } else { + index = string_set__add_entry(s, key); + if (fr.entry_prev != MAP_SENTINEL) { + s->entries[fr.entry_prev].next = index; + } else { + s->hashes[fr.hash_index] = index; + } + } + s->entries[index].value = str; + + if (string_set__full(s)) { + string_set_grow(s); + } + return exists; +} + void string_set__erase(StringSet *s, MapFindResult fr) { MapFindResult last; diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index 636632d71..f6b4b10b8 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -14,6 +14,7 @@ package test_core_crypto import "core:testing" import "core:fmt" +import "core:strings" import "core:crypto/md2" import "core:crypto/md4" @@ -230,11 +231,14 @@ test_sha224 :: proc(t: ^testing.T) { // Test vectors from // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf // https://www.di-mgt.com.au/sha_testvectors.html + // https://datatracker.ietf.org/doc/html/rfc3874#section-3.3 + data_1_000_000_a := strings.repeat("a", 1_000_000) test_vectors := [?]TestHash { TestHash{"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", ""}, TestHash{"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", "abc"}, TestHash{"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"}, TestHash{"c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"}, + TestHash{"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67", data_1_000_000_a}, } for v, _ in test_vectors { computed := sha2.hash_224(v.str) diff --git a/tests/core/hash/test_core_hash.odin b/tests/core/hash/test_core_hash.odin index 607642339..e69490143 100644 --- a/tests/core/hash/test_core_hash.odin +++ b/tests/core/hash/test_core_hash.odin @@ -6,6 +6,8 @@ import "core:time" import "core:testing" import "core:fmt" import "core:os" +import "core:math/rand" +import "core:intrinsics" TEST_count := 0 TEST_fail := 0 @@ -31,8 +33,10 @@ when ODIN_TEST { main :: proc() { t := testing.T{} test_benchmark_runner(&t) - test_xxhash_vectors(&t) test_crc64_vectors(&t) + test_xxhash_vectors(&t) + test_xxhash_large(&t) + fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) if TEST_fail > 0 { os.exit(1) @@ -191,6 +195,104 @@ test_benchmark_runner :: proc(t: ^testing.T) { benchmark_print(name, options) } +@test +test_xxhash_large :: proc(t: ^testing.T) { + many_zeroes := make([]u8, 16 * 1024 * 1024) + defer delete(many_zeroes) + + // All at once. + for i, v in ZERO_VECTORS { + b := many_zeroes[:i] + + fmt.printf("[test_xxhash_large] All at once. Size: %v\n", i) + + xxh32 := xxhash.XXH32(b) + xxh64 := xxhash.XXH64(b) + xxh3_64 := xxhash.XXH3_64(b) + xxh3_128 := xxhash.XXH3_128(b) + + xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) + xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) + xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) + xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) + + expect(t, xxh32 == v.xxh_32, xxh32_error) + expect(t, xxh64 == v.xxh_64, xxh64_error) + expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) + expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + } + + when #config(RAND_STATE, -1) >= 0 && #config(RAND_INC, -1) >= 0 { + random_seed := rand.Rand{ + state = u64(#config(RAND_STATE, -1)), + inc = u64(#config(RAND_INC, -1)), + } + fmt.printf("Using user-selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc) + } else { + random_seed := rand.create(u64(intrinsics.read_cycle_counter())) + fmt.printf("Randonly selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc) + } + + // Streamed + for i, v in ZERO_VECTORS { + b := many_zeroes[:i] + + fmt.printf("[test_xxhash_large] Streamed. Size: %v\n", i) + + // bytes_per_update := []int{1, 42, 13, 7, 16, 5, 23, 74, 1024, 511, 1023, 47} + // update_size_idx: int + + xxh_32_state, xxh_32_err := xxhash.XXH32_create_state() + defer xxhash.XXH32_destroy_state(xxh_32_state) + expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state.") + + xxh_64_state, xxh_64_err := xxhash.XXH64_create_state() + defer xxhash.XXH64_destroy_state(xxh_64_state) + expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state.") + + xxh3_64_state, xxh3_64_err := xxhash.XXH3_create_state() + defer xxhash.XXH3_destroy_state(xxh3_64_state) + expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state.") + + xxh3_128_state, xxh3_128_err := xxhash.XXH3_create_state() + defer xxhash.XXH3_destroy_state(xxh3_128_state) + expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state.") + + // XXH3_128_update + + for len(b) > 0 { + update_size := min(len(b), rand.int_max(8192, &random_seed)) + if update_size > 4096 { + update_size %= 73 + } + xxhash.XXH32_update (xxh_32_state, b[:update_size]) + xxhash.XXH64_update (xxh_64_state, b[:update_size]) + + xxhash.XXH3_64_update (xxh3_64_state, b[:update_size]) + xxhash.XXH3_128_update(xxh3_128_state, b[:update_size]) + + b = b[update_size:] + } + + // Now finalize + xxh32 := xxhash.XXH32_digest(xxh_32_state) + xxh64 := xxhash.XXH64_digest(xxh_64_state) + + xxh3_64 := xxhash.XXH3_64_digest(xxh3_64_state) + xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state) + + xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) + xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) + xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) + xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) + + expect(t, xxh32 == v.xxh_32, xxh32_error) + expect(t, xxh64 == v.xxh_64, xxh64_error) + expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) + expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + } +} + @test test_xxhash_vectors :: proc(t: ^testing.T) { fmt.println("Verifying against XXHASH_TEST_VECTOR_SEEDED:") diff --git a/tests/core/hash/test_vectors_xxhash.odin b/tests/core/hash/test_vectors_xxhash.odin index dccda5e53..6a37aef30 100644 --- a/tests/core/hash/test_vectors_xxhash.odin +++ b/tests/core/hash/test_vectors_xxhash.odin @@ -3,7 +3,7 @@ */ package test_core_hash -XXHASH_Test_Vectors_With_Seed :: struct #packed { +XXHASH_Test_Vectors :: struct #packed { /* Old hashes */ @@ -17,7 +17,75 @@ XXHASH_Test_Vectors_With_Seed :: struct #packed { xxh3_128: u128, } -XXHASH_TEST_VECTOR_SEEDED := map[u64][257]XXHASH_Test_Vectors_With_Seed{ +ZERO_VECTORS := map[int]XXHASH_Test_Vectors{ + 1024 * 1024 = { + /* + Old hashes + */ + xxh_32 = 0x9430f97f, // xxhsum -H0 + xxh_64 = 0x87d2a1b6e1163ef1, // xxhsum -H1 + + /* + XXH3 hashes + */ + xxh3_128 = 0xb6ef17a3448492b6918780b90550bf34, // xxhsum -H2 + xxh3_64 = 0x918780b90550bf34, // xxhsum -H3 + }, + 1024 * 2048 = { + /* + Old hashes + */ + xxh_32 = 0xeeb74ca1, // xxhsum -H0 + xxh_64 = 0xeb8a7322f88e23db, // xxhsum -H1 + + /* + XXH3 hashes + */ + xxh3_128 = 0x7b3e6abe1456fd0094e26d8e04364852, // xxhsum -H2 + xxh3_64 = 0x94e26d8e04364852, // xxhsum -H3 + }, + 1024 * 4096 = { + /* + Old hashes + */ + xxh_32 = 0xa59010b8, // xxhsum -H0 + xxh_64 = 0x639f9e1a7cbc9d28, // xxhsum -H1 + + /* + XXH3 hashes + */ + xxh3_128 = 0x34001ae2f947e773165f453a5f35c459, // xxhsum -H2 + xxh3_64 = 0x165f453a5f35c459, // xxhsum -H3 + }, + 1024 * 8192 = { + /* + Old hashes + */ + xxh_32 = 0xfed1d084, // xxhsum -H0 + xxh_64 = 0x86823cbc61f6df0f, // xxhsum -H1 + + /* + XXH3 hashes + */ + xxh3_128 = 0x9d6bf1a4e92df02ce881a25e37e37b19, // xxhsum -H2 + xxh3_64 = 0xe881a25e37e37b19, // xxhsum -H3 + }, + 1024 * 16384 = { + /* + Old hashes + */ + xxh_32 = 0x0ee4ebf9, // xxhsum -H0 + xxh_64 = 0x412f1e415ee2d80b, // xxhsum -H1 + + /* + XXH3 hashes + */ + xxh3_128 = 0x14d914cac1f4c1b1c4979470a1b529a1, // xxhsum -H2 + xxh3_64 = 0xc4979470a1b529a1, // xxhsum -H3 + }, +} + +XXHASH_TEST_VECTOR_SEEDED := map[u64][257]XXHASH_Test_Vectors{ 0 = { { // Length: 000 /* XXH32 with seed */ 0x02cc5d05, diff --git a/tests/vendor/botan/test_vendor_botan.odin b/tests/vendor/botan/test_vendor_botan.odin index f0ff44ac9..0a93723c8 100644 --- a/tests/vendor/botan/test_vendor_botan.odin +++ b/tests/vendor/botan/test_vendor_botan.odin @@ -15,6 +15,7 @@ package test_vendor_botan import "core:testing" import "core:fmt" import "core:os" +import "core:strings" import "vendor:botan/md4" import "vendor:botan/md5" @@ -172,11 +173,14 @@ test_sha224 :: proc(t: ^testing.T) { // Test vectors from // https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/sha_all.pdf // https://www.di-mgt.com.au/sha_testvectors.html + // https://datatracker.ietf.org/doc/html/rfc3874#section-3.3 + data_1_000_000_a := strings.repeat("a", 1_000_000) test_vectors := [?]TestHash { TestHash{"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", ""}, TestHash{"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7", "abc"}, TestHash{"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"}, TestHash{"c97ca9a559850ce97a04a96def6d99a9e0e0e2ab14e6b8df265fc0b3", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"}, + TestHash{"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67", data_1_000_000_a}, } for v, _ in test_vectors { computed := sha2.hash_224(v.str) diff --git a/vendor/OpenEXRCore/LICENSE.md b/vendor/OpenEXRCore/LICENSE.md new file mode 100644 index 000000000..32e8c226a --- /dev/null +++ b/vendor/OpenEXRCore/LICENSE.md @@ -0,0 +1,11 @@ +Copyright (c) Contributors to the OpenEXR Project. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/OpenEXRCore/OpenEXRCore-3_1.lib b/vendor/OpenEXRCore/OpenEXRCore-3_1.lib new file mode 100644 index 000000000..f70938101 Binary files /dev/null and b/vendor/OpenEXRCore/OpenEXRCore-3_1.lib differ diff --git a/vendor/OpenEXRCore/exr_attr.odin b/vendor/OpenEXRCore/exr_attr.odin new file mode 100644 index 000000000..eb07142ec --- /dev/null +++ b/vendor/OpenEXRCore/exr_attr.odin @@ -0,0 +1,397 @@ +package vendor_openexr + +import "core:c" + +// Enum declaring allowed values for \c u8 value stored in built-in compression type. +compression_t :: enum c.int { + NONE = 0, + RLE = 1, + ZIPS = 2, + ZIP = 3, + PIZ = 4, + PXR24 = 5, + B44 = 6, + B44A = 7, + DWAA = 8, + DWAB = 9, +} + +// Enum declaring allowed values for \c u8 value stored in built-in env map type. +envmap_t :: enum c.int { + LATLONG = 0, + CUBE = 1, +} + +// Enum declaring allowed values for \c u8 value stored in \c lineOrder type. +lineorder_t :: enum c.int { + INCREASING_Y = 0, + DECREASING_Y = 1, + RANDOM_Y = 2, +} + +// Enum declaring allowed values for part type. +storage_t :: enum c.int { + SCANLINE = 0, // Corresponds to type of \c scanlineimage. + TILED, // Corresponds to type of \c tiledimage. + DEEP_SCANLINE, // Corresponds to type of \c deepscanline. + DEEP_TILED, // Corresponds to type of \c deeptile. +} + +// @brief Enum representing what type of tile information is contained. +tile_level_mode_t :: enum c.int { + ONE_LEVEL = 0, // Single level of image data. + MIPMAP_LEVELS = 1, // Mipmapped image data. + RIPMAP_LEVELS = 2, // Ripmapped image data. +} + +/** @brief Enum representing how to scale positions between levels. */ +tile_round_mode_t :: enum c.int { + DOWN = 0, + UP = 1, +} + +/** @brief Enum capturing the underlying data type on a channel. */ +pixel_type_t :: enum c.int { + UINT = 0, + HALF = 1, + FLOAT = 2, +} + +/* /////////////////////////////////////// */ +/* First set of structs are data where we can read directly with no allocation needed... */ + +/** @brief Struct to hold color chromaticities to interpret the tristimulus color values in the image data. */ +attr_chromaticities_t :: struct #packed { + red_x: f32, + red_y: f32, + green_x: f32, + green_y: f32, + blue_x: f32, + blue_y: f32, + white_x: f32, + white_y: f32, +} + +/** @brief Struct to hold keycode information. */ +attr_keycode_t :: struct #packed { + film_mfc_code: i32, + film_type: i32, + prefix: i32, + count: i32, + perf_offset: i32, + perfs_per_frame: i32, + perfs_per_count: i32, +} + +/** @brief struct to hold a 32-bit floating-point 3x3 matrix. */ +attr_m33f_t :: struct #packed { + m: [9]f32, +} + +/** @brief struct to hold a 64-bit floating-point 3x3 matrix. */ +attr_m33d_t :: struct #packed { + m: [9]f64, +} + +/** @brief Struct to hold a 32-bit floating-point 4x4 matrix. */ +attr_m44f_t :: struct #packed { + m: [16]f32, +} + +/** @brief Struct to hold a 64-bit floating-point 4x4 matrix. */ +attr_m44d_t :: struct #packed { + m: [16]f64, +} + +/** @brief Struct to hold an integer ratio value. */ +attr_rational_t :: struct #packed { + num: i32, + denom: u32, +} + +/** @brief Struct to hold timecode information. */ +attr_timecode_t :: struct #packed { + time_and_flags: u32, + user_data: u32, +} + +/** @brief Struct to hold a 2-element integer vector. */ +attr_v2i_t :: distinct [2]i32 + +/** @brief Struct to hold a 2-element 32-bit float vector. */ +attr_v2f_t :: distinct [2]f32 + +/** @brief Struct to hold a 2-element 64-bit float vector. */ +attr_v2d_t :: distinct [2]f64 + +/** @brief Struct to hold a 3-element integer vector. */ +attr_v3i_t :: distinct [3]i32 + +/** @brief Struct to hold a 3-element 32-bit float vector. */ +attr_v3f_t :: distinct [3]f32 + +/** @brief Struct to hold a 3-element 64-bit float vector. */ +attr_v3d_t :: distinct [3]f64 + +/** @brief Struct to hold an integer box/region definition. */ +attr_box2i_t :: struct #packed { + min: attr_v2i_t, + max: attr_v2i_t, +} + +/** @brief Struct to hold a floating-point box/region definition. */ +attr_box2f_t:: struct #packed { + min: attr_v2f_t, + max: attr_v2f_t, +} + +/** @brief Struct holding base tiledesc attribute type defined in spec + * + * NB: This is in a tightly packed area so it can be read directly, be + * careful it doesn't become padded to the next \c uint32_t boundary. + */ +attr_tiledesc_t :: struct #packed { + x_size: u32, + y_size: u32, + level_and_round: u8, +} + +/** @brief Macro to access type of tiling from packed structure. */ +GET_TILE_LEVEL_MODE :: #force_inline proc "c" (tiledesc: attr_tiledesc_t) -> tile_level_mode_t { + return tile_level_mode_t(tiledesc.level_and_round & 0xf) +} +/** @brief Macro to access the rounding mode of tiling from packed structure. */ +GET_TILE_ROUND_MODE :: #force_inline proc "c" (tiledesc: attr_tiledesc_t) -> tile_round_mode_t { + return tile_round_mode_t((tiledesc.level_and_round >> 4) & 0xf) +} +/** @brief Macro to pack the tiling type and rounding mode into packed structure. */ +PACK_TILE_LEVEL_ROUND :: #force_inline proc "c" (lvl: tile_level_mode_t, mode: tile_round_mode_t) -> u8 { + return ((u8(mode) & 0xf) << 4) | (u8(lvl) & 0xf) +} + + +/* /////////////////////////////////////// */ +/* Now structs that involve heap allocation to store data. */ + +/** Storage for a string. */ +attr_string_t :: struct { + length: i32, + /** If this is non-zero, the string owns the data, if 0, is a const ref to a static string. */ + alloc_size: i32, + + str: cstring, +} + +/** Storage for a string vector. */ +attr_string_vector_t :: struct { + n_strings: i32, + /** If this is non-zero, the string vector owns the data, if 0, is a const ref. */ + alloc_size: i32, + + strings: [^]attr_string_t, +} + +/** Float vector storage struct. */ +attr_float_vector_t :: struct { + length: i32, + /** If this is non-zero, the float vector owns the data, if 0, is a const ref. */ + alloc_size: i32, + + arr: [^]f32, +} + +/** Hint for lossy compression methods about how to treat values + * (logarithmic or linear), meaning a human sees values like R, G, B, + * luminance difference between 0.1 and 0.2 as about the same as 1.0 + * to 2.0 (logarithmic), where chroma coordinates are closer to linear + * (0.1 and 0.2 is about the same difference as 1.0 and 1.1). + */ +perceptual_treatment_t :: enum c.int { + LOGARITHMIC = 0, + LINEAR = 1, +} + +/** Individual channel information. */ +attr_chlist_entry_t :: struct { + name: attr_string_t, + /** Data representation for these pixels: uint, half, float. */ + pixel_type: pixel_type_t, + /** Possible values are 0 and 1 per docs perceptual_treatment_t. */ + p_linear: u8, + reserved: [3]u8, + x_sampling: i32, + y_sampling: i32, +} + +/** List of channel information (sorted alphabetically). */ +attr_chlist_t :: struct { + num_channels: c.int, + num_alloced: c.int, + + entries: [^]attr_chlist_entry_t, +} + +/** @brief Struct to define attributes of an embedded preview image. */ +attr_preview_t :: struct { + width: u32, + height: u32, + /** If this is non-zero, the preview owns the data, if 0, is a const ref. */ + alloc_size: c.size_t, + + rgba: [^]u8, +} + +/** Custom storage structure for opaque data. + * + * Handlers for opaque types can be registered, then when a + * non-builtin type is encountered with a registered handler, the + * function pointers to unpack/pack it will be set up. + * + * @sa register_attr_type_handler + */ +attr_opaquedata_t :: struct { + size: i32, + unpacked_size: i32, + /** If this is non-zero, the struct owns the data, if 0, is a const ref. */ + packed_alloc_size: i32, + pad: [4]u8, + + packed_data: rawptr, + + /** When an application wants to have custom data, they can store + * an unpacked form here which will be requested to be destroyed + * upon destruction of the attribute. + */ + unpacked_data: rawptr, + + /** An application can register an attribute handler which then + * fills in these function pointers. This allows a user to delay + * the expansion of the custom type until access is desired, and + * similarly, to delay the packing of the data until write time. + */ + unpack_func_ptr: proc "c" ( + ctxt: context_t, + data: rawptr, + attrsize: i32, + outsize: ^i32, + outbuffer: ^rawptr) -> result_t, + pack_func_ptr: proc "c" ( + ctxt: context_t, + data: rawptr, + datasize: i32, + outsize: ^i32, + outbuffer: rawptr) -> result_t, + destroy_unpacked_func_ptr: proc "c" ( + ctxt: context_t, data: rawptr, attrsize: i32), +} + +/* /////////////////////////////////////// */ + +/** @brief Built-in/native attribute type enum. + * + * This will enable us to do a tagged type struct to generically store + * attributes. + */ +attribute_type_t :: enum c.int { + UNKNOWN = 0, // Type indicating an error or uninitialized attribute. + BOX2I, // Integer region definition. @see attr_box2i_t. + BOX2F, // Float region definition. @see attr_box2f_t. + CHLIST, // Definition of channels in file @see chlist_entry. + CHROMATICITIES, // Values to specify color space of colors in file @see attr_chromaticities_t. + COMPRESSION, // ``u8`` declaring compression present. + DOUBLE, // Double precision floating point number. + ENVMAP, // ``u8`` declaring environment map type. + FLOAT, // Normal (4 byte) precision floating point number. + FLOAT_VECTOR, // List of normal (4 byte) precision floating point numbers. + INT, // 32-bit signed integer value. + KEYCODE, // Struct recording keycode @see attr_keycode_t. + LINEORDER, // ``u8`` declaring scanline ordering. + M33F, // 9 32-bit floats representing a 3x3 matrix. + M33D, // 9 64-bit floats representing a 3x3 matrix. + M44F, // 16 32-bit floats representing a 4x4 matrix. + M44D, // 16 64-bit floats representing a 4x4 matrix. + PREVIEW, // 2 ``unsigned ints`` followed by 4 x w x h ``u8`` image. + RATIONAL, // \c int followed by ``unsigned int`` + STRING, // ``int`` (length) followed by char string data. + STRING_VECTOR, // 0 or more text strings (int + string). number is based on attribute size. + TILEDESC, // 2 ``unsigned ints`` ``xSize``, ``ySize`` followed by mode. + TIMECODE, // 2 ``unsigned ints`` time and flags, user data. + V2I, // Pair of 32-bit integers. + V2F, // Pair of 32-bit floats. + V2D, // Pair of 64-bit floats. + V3I, // Set of 3 32-bit integers. + V3F, // Set of 3 32-bit floats. + V3D, // Set of 3 64-bit floats. + OPAQUE, // User/unknown provided type. +} + +/** @brief Storage, name and type information for an attribute. + * + * Attributes (metadata) for the file cause a surprising amount of + * overhead. It is not uncommon for a production-grade EXR to have + * many attributes. As such, the attribute struct is designed in a + * slightly more complicated manner. It is optimized to have the + * storage for that attribute: the struct itself, the name, the type, + * and the data all allocated as one block. Further, the type and + * standard names may use a static string to avoid allocating space + * for those as necessary with the pointers pointing to static strings + * (not to be freed). Finally, small values are optimized for. + */ +attribute_t :: struct { + /** Name of the attribute. */ + name: cstring, + /** String type name of the attribute. */ + type_name: cstring, + /** Length of name string (short flag is 31 max, long allows 255). */ + name_length: u8, + /** Length of type string (short flag is 31 max, long allows 255). */ + type_name_length: u8, + + pad: [2]u8, + + /** Enum of the attribute type. */ + type: attribute_type_t, + + /** Union of pointers of different types that can be used to type + * pun to an appropriate type for builtins. Do note that while + * this looks like a big thing, it is only the size of a single + * pointer. These are all pointers into some other data block + * storing the value you want, with the exception of the pod types + * which are just put in place (i.e. small value optimization). + * + * The attribute type \c type should directly correlate to one + * of these entries. + */ + using _: struct #raw_union { + // NB: not pointers for POD types + uc: u8, + d: f64, + f: f32, + i: i32, + + box2i: ^attr_box2i_t, + box2f: ^attr_box2f_t, + chlist: ^attr_chlist_t, + chromaticities: ^attr_chromaticities_t, + keycode: ^attr_keycode_t, + floatvector: ^attr_float_vector_t, + m33f: ^attr_m33f_t, + m33d: ^attr_m33d_t, + m44f: ^attr_m44f_t, + m44d: ^attr_m44d_t, + preview: ^attr_preview_t, + rational: ^attr_rational_t, + string: ^attr_string_t, + stringvector: ^attr_string_vector_t, + tiledesc: ^attr_tiledesc_t, + timecode: ^attr_timecode_t, + v2i: ^attr_v2i_t, + v2f: ^attr_v2f_t, + v2d: ^attr_v2d_t, + v3i: ^attr_v3i_t, + v3f: ^attr_v3f_t, + v3d: ^attr_v3d_t, + opaque: ^attr_opaquedata_t, + rawptr: ^u8, + }, +} \ No newline at end of file diff --git a/vendor/OpenEXRCore/exr_base.odin b/vendor/OpenEXRCore/exr_base.odin new file mode 100644 index 000000000..3c71f6285 --- /dev/null +++ b/vendor/OpenEXRCore/exr_base.odin @@ -0,0 +1,174 @@ +package vendor_openexr + +when ODIN_OS == .Windows { + foreign import lib "OpenEXRCore-3_1.lib" +} else { + foreign import lib "system:OpenEXRCore-3_1" +} + +import "core:c" + +/** @brief Function pointer used to hold a malloc-like routine. + * + * Providing these to a context will override what memory is used to + * allocate the context itself, as well as any allocations which + * happen during processing of a file or stream. This can be used by + * systems which provide rich malloc tracking routines to override the + * internal allocations performed by the library. + * + * This function is expected to allocate and return a new memory + * handle, or `NULL` if allocation failed (which the library will then + * handle and return an out-of-memory error). + * + * If one is provided, both should be provided. + * @sa exr_memory_free_func_t + */ +memory_allocation_func_t :: proc "c" (bytes: c.size_t) -> rawptr + +/** @brief Function pointer used to hold a free-like routine. + * + * Providing these to a context will override what memory is used to + * allocate the context itself, as well as any allocations which + * happen during processing of a file or stream. This can be used by + * systems which provide rich malloc tracking routines to override the + * internal allocations performed by the library. + * + * This function is expected to return memory to the system, ala free + * from the C library. + * + * If providing one, probably need to provide both routines. + * @sa exr_memory_allocation_func_t + */ +memory_free_func_t :: proc "c" (ptr: rawptr) + +@(link_prefix="exr_", default_calling_convention="c") +foreign lib { + /** @brief Retrieve the current library version. The @p extra string is for + * custom installs, and is a static string, do not free the returned + * pointer. + */ + get_library_version :: proc(maj, min, patch: ^c.int, extra: ^cstring) --- + + /** @brief Limit the size of image allowed to be parsed or created by + * the library. + * + * This is used as a safety check against corrupt files, but can also + * serve to avoid potential issues on machines which have very + * constrained RAM. + * + * These values are among the only globals in the core layer of + * OpenEXR. The intended use is for applications to define a global + * default, which will be combined with the values provided to the + * individual context creation routine. The values are used to check + * against parsed header values. This adds some level of safety from + * memory overruns where a corrupt file given to the system may cause + * a large allocation to happen, enabling buffer overruns or other + * potential security issue. + * + * These global values are combined with the values in + * \ref exr_context_initializer_t using the following rules: + * + * 1. negative values are ignored. + * + * 2. if either value has a positive (non-zero) value, and the other + * has 0, the positive value is preferred. + * + * 3. If both are positive (non-zero), the minimum value is used. + * + * 4. If both values are 0, this disables the constrained size checks. + * + * This function does not fail. + */ + set_default_maximum_image_size :: proc(w, h: c.int) --- + + /** @brief Retrieve the global default maximum image size. + * + * This function does not fail. + */ + get_default_maximum_image_size :: proc(w, h: ^c.int) --- + + /** @brief Limit the size of an image tile allowed to be parsed or + * created by the library. + * + * Similar to image size, this places constraints on the maximum tile + * size as a safety check against bad file data + * + * This is used as a safety check against corrupt files, but can also + * serve to avoid potential issues on machines which have very + * constrained RAM + * + * These values are among the only globals in the core layer of + * OpenEXR. The intended use is for applications to define a global + * default, which will be combined with the values provided to the + * individual context creation routine. The values are used to check + * against parsed header values. This adds some level of safety from + * memory overruns where a corrupt file given to the system may cause + * a large allocation to happen, enabling buffer overruns or other + * potential security issue. + * + * These global values are combined with the values in + * \ref exr_context_initializer_t using the following rules: + * + * 1. negative values are ignored. + * + * 2. if either value has a positive (non-zero) value, and the other + * has 0, the positive value is preferred. + * + * 3. If both are positive (non-zero), the minimum value is used. + * + * 4. If both values are 0, this disables the constrained size checks. + * + * This function does not fail. + */ + set_default_maximum_tile_size :: proc(w, h: c.int) --- + + /** @brief Retrieve the global maximum tile size. + * + * This function does not fail. + */ + get_default_maximum_tile_size :: proc(w, h: ^c.int) --- + + /** @} */ + + /** + * @defgroup CompressionDefaults Provides default compression settings + * @{ + */ + + /** @brief Assigns a default zip compression level. + * + * This value may be controlled separately on each part, but this + * global control determines the initial value. + */ + set_default_zip_compression_level :: proc(l: c.int) --- + + /** @brief Retrieve the global default zip compression value + */ + get_default_zip_compression_level :: proc(l: ^c.int) --- + + /** @brief Assigns a default DWA compression quality level. + * + * This value may be controlled separately on each part, but this + * global control determines the initial value. + */ + set_default_dwa_compression_quality :: proc(q: f32) --- + + /** @brief Retrieve the global default dwa compression quality + */ + get_default_dwa_compression_quality :: proc(q: ^f32) --- + + /** @brief Allow the user to override default allocator used internal + * allocations necessary for files, attributes, and other temporary + * memory. + * + * These routines may be overridden when creating a specific context, + * however this provides global defaults such that the default can be + * applied. + * + * If either pointer is 0, the appropriate malloc/free routine will be + * substituted. + * + * This function does not fail. + */ + set_default_memory_routines :: proc(alloc_func: memory_allocation_func_t, free_func: memory_free_func_t) --- +} \ No newline at end of file diff --git a/vendor/OpenEXRCore/exr_chunkio.odin b/vendor/OpenEXRCore/exr_chunkio.odin new file mode 100644 index 000000000..e5fae15f5 --- /dev/null +++ b/vendor/OpenEXRCore/exr_chunkio.odin @@ -0,0 +1,147 @@ +package vendor_openexr + +when ODIN_OS == .Windows { + foreign import lib "OpenEXRCore-3_1.lib" +} else { + foreign import lib "system:OpenEXRCore-3_1" +} + +import "core:c" + +/** + * Struct describing raw data information about a chunk. + * + * A chunk is the generic term for a pixel data block in an EXR file, + * as described in the OpenEXR File Layout documentation. This is + * common between all different forms of data that can be stored. + */ +chunk_info_t :: struct { + idx: i32, + + /** For tiles, this is the tilex; for scans it is the x. */ + start_x: i32, + /** For tiles, this is the tiley; for scans it is the scanline y. */ + start_y: i32, + height: i32, /**< For this chunk. */ + width: i32, /**< For this chunk. */ + + level_x: u8, /**< For tiled files. */ + level_y: u8, /**< For tiled files. */ + + type: u8, + compression: u8, + + data_offset: u64, + packed_size: u64, + unpacked_size: u64, + + sample_count_data_offset: u64, + sample_count_table_size: u64, +} + +@(link_prefix="exr_", default_calling_convention="c") +foreign lib { + read_scanline_chunk_info :: proc(ctxt: const_context_t, part_index: c.int, y: c.int, cinfo: ^chunk_info_t) -> result_t --- + + read_tile_chunk_info :: proc( + ctxt: const_context_t, + part_index: c.int, + tilex: c.int, + tiley: c.int, + levelx: c.int, + levely: c.int, + cinfo: ^chunk_info_t) -> result_t --- + + /** Read the packed data block for a chunk. + * + * This assumes that the buffer pointed to by @p packed_data is + * large enough to hold the chunk block info packed_size bytes. + */ + read_chunk :: proc( + ctxt: const_context_t, + part_index: c.int, + cinfo: ^chunk_info_t, + packed_data: rawptr) -> result_t --- + + /** + * Read chunk for deep data. + * + * This allows one to read the packed data, the sample count data, or both. + * \c exr_read_chunk also works to read deep data packed data, + * but this is a routine to get the sample count table and the packed + * data in one go, or if you want to pre-read the sample count data, + * you can get just that buffer. + */ + read_deep_chunk :: proc( + ctxt: const_context_t, + part_index: c.int, + cinfo: ^chunk_info_t, + packed_data: rawptr, + sample_data: rawptr) -> result_t --- + + /**************************************/ + + /** Initialize a \c chunk_info_t structure when encoding scanline + * data (similar to read but does not do anything with a chunk + * table). + */ + write_scanline_chunk_info :: proc(ctxt: context_t, part_index: c.int, y: c.int, cinfo: ^chunk_info_t) -> result_t --- + + /** Initialize a \c chunk_info_t structure when encoding tiled data + * (similar to read but does not do anything with a chunk table). + */ + write_tile_chunk_info :: proc( + ctxt: context_t, + part_index: c.int, + tilex: c.int, + tiley: c.int, + levelx: c.int, + levely: c.int, + cinfo: ^chunk_info_t) -> result_t --- + + /** + * @p y must the appropriate starting y for the specified chunk. + */ + write_scanline_chunk :: proc( + ctxt: context_t, + part_index: int, + y: int, + packed_data: rawptr, + packed_size: u64) -> result_t --- + + /** + * @p y must the appropriate starting y for the specified chunk. + */ + write_deep_scanline_chunk :: proc( + ctxt: context_t, + part_index: c.int, + y: c.int, + packed_data: rawptr, + packed_size: u64, + unpacked_size: u64, + sample_data: rawptr, + sample_data_size: u64) -> result_t --- + + write_tile_chunk :: proc( + ctxt: context_t, + part_index: c.int, + tilex: c.int, + tiley: c.int, + levelx: c.int, + levely: c.int, + packed_data: rawptr, + packed_size: u64) -> result_t --- + + write_deep_tile_chunk :: proc( + ctxt: context_t, + part_index: c.int, + tilex: c.int, + tiley: c.int, + levelx: c.int, + levely: c.int, + packed_data: rawptr, + packed_size: u64, + unpacked_size: u64, + sample_data: rawptr, + sample_data_size: u64) -> result_t --- +} \ No newline at end of file diff --git a/vendor/OpenEXRCore/exr_coding.odin b/vendor/OpenEXRCore/exr_coding.odin new file mode 100644 index 000000000..337475edf --- /dev/null +++ b/vendor/OpenEXRCore/exr_coding.odin @@ -0,0 +1,119 @@ +package vendor_openexr + +import "core:c" +/** + * Enum for use in a custom allocator in the encode/decode pipelines + * (that is, so the implementor knows whether to allocate on which + * device based on the buffer disposition). + */ +transcoding_pipeline_buffer_id_t :: enum c.int { + PACKED, + UNPACKED, + COMPRESSED, + SCRATCH1, + SCRATCH2, + PACKED_SAMPLES, + SAMPLES, +} + +/** @brief Struct for negotiating buffers when decoding/encoding + * chunks of data. + * + * This is generic and meant to negotiate exr data bi-directionally, + * in that the same structure is used for both decoding and encoding + * chunks for read and write, respectively. + * + * The first half of the structure will be filled by the library, and + * the caller is expected to fill the second half appropriately. + */ +coding_channel_info_t :: struct { + /************************************************** + * Elements below are populated by the library when + * decoding is initialized/updated and must be left + * untouched when using the default decoder routines. + **************************************************/ + + /** Channel name. + * + * This is provided as a convenient reference. Do not free, this + * refers to the internal data structure in the context. + */ + channel_name: cstring, + + /** Number of lines for this channel in this chunk. + * + * May be 0 or less than overall image height based on sampling + * (i.e. when in 4:2:0 type sampling) + */ + height: i32, + + /** Width in pixel count. + * + * May be 0 or less than overall image width based on sampling + * (i.e. 4:2:2 will have some channels have fewer values). + */ + width: i32, + + /** Horizontal subsampling information. */ + x_samples: i32, + /** Vertical subsampling information. */ + y_samples: i32, + + /** Linear flag from channel definition (used by b44). */ + p_linear: u8, + + /** How many bytes per pixel this channel consumes (2 for float16, + * 4 for float32/uint32). + */ + bytes_per_element: i8, + + /** Small form of exr_pixel_type_t enum (EXR_PIXEL_UINT/HALF/FLOAT). */ + data_type: u16, + + /************************************************** + * Elements below must be edited by the caller + * to control encoding/decoding. + **************************************************/ + + /** How many bytes per pixel the input is or output should be + * (2 for float16, 4 for float32/uint32). Defaults to same + * size as input. + */ + user_bytes_per_element: i16, + + /** Small form of exr_pixel_type_t enum + * (EXR_PIXEL_UINT/HALF/FLOAT). Defaults to same type as input. + */ + user_data_type: u16, + + /** Increment to get to next pixel. + * + * This is in bytes. Must be specified when the decode pointer is + * specified (and always for encode). + * + * This is useful for implementing transcoding generically of + * planar or interleaved data. For planar data, where the layout + * is RRRRRGGGGGBBBBB, you can pass in 1 * bytes per component. + */ + + user_pixel_stride: i32, + + /** When \c lines > 1 for a chunk, this is the increment used to get + * from beginning of line to beginning of next line. + * + * This is in bytes. Must be specified when the decode pointer is + * specified (and always for encode). + */ + user_line_stride: i32, + + /** This data member has different requirements reading vs + * writing. When reading, if this is left as `NULL`, the channel + * will be skipped during read and not filled in. During a write + * operation, this pointer is considered const and not + * modified. To make this more clear, a union is used here. + */ + using _: struct #raw_union { + decode_to_ptr: ^u8, + encode_from_ptr: ^u8, + }, +} diff --git a/vendor/OpenEXRCore/exr_context.odin b/vendor/OpenEXRCore/exr_context.odin new file mode 100644 index 000000000..4b70950b3 --- /dev/null +++ b/vendor/OpenEXRCore/exr_context.odin @@ -0,0 +1,489 @@ +package vendor_openexr + +when ODIN_OS == .Windows { + foreign import lib "OpenEXRCore-3_1.lib" +} else { + foreign import lib "system:OpenEXRCore-3_1" +} + +import "core:c" + +#assert(size_of(c.int) == size_of(b32)) + +context_t :: distinct rawptr +const_context_t :: context_t + +/** + * @defgroup ContextFunctions OpenEXR Context Stream/File Functions + * + * @brief These are a group of function interfaces used to customize + * the error handling, memory allocations, or I/O behavior of an + * OpenEXR context. + * + * @{ + */ + +/** @brief Stream error notifier + * + * This function pointer is provided to the stream functions by the + * library such that they can provide a nice error message to the + * user during stream operations. + */ +stream_error_func_ptr_t :: proc "c" (ctxt: const_context_t, code: result_t, fmt: cstring, #c_vararg args: ..any) -> result_t + +/** @brief Error callback function + * + * Because a file can be read from using many threads at once, it is + * difficult to store an error message for later retrieval. As such, + * when a file is constructed, a callback function can be provided + * which delivers an error message for the calling application to + * handle. This will then be delivered on the same thread causing the + * error. + */ +error_handler_cb_t :: proc "c" (ctxt: const_context_t, code: result_t, msg: cstring) + +/** Destroy custom stream function pointer + * + * Generic callback to clean up user data for custom streams. + * This is called when the file is closed and expected not to + * error. + * + * @param failed Indicates the write operation failed, the + * implementor may wish to cleanup temporary files + */ +destroy_stream_func_ptr_t :: proc "c" (ctxt: const_context_t, userdata: rawptr, failed: c.int) + +/** Query stream size function pointer + * + * Used to query the size of the file, or amount of data representing + * the openexr file in the data stream. + * + * This is used to validate requests against the file. If the size is + * unavailable, return -1, which will disable these validation steps + * for this file, although appropriate memory safeguards must be in + * place in the calling application. + */ +query_size_func_ptr_t :: proc "c" (ctxt: const_context_t, userdata: rawptr) -> i64 + +/** @brief Read custom function pointer + * + * Used to read data from a custom output. Expects similar semantics to + * pread or ReadFile with overlapped data under win32. + * + * It is required that this provides thread-safe concurrent access to + * the same file. If the stream/input layer you are providing does + * not have this guarantee, your are responsible for providing + * appropriate serialization of requests. + * + * A file should be expected to be accessed in the following pattern: + * - upon open, the header and part information attributes will be read + * - upon the first image read request, the offset tables will be read + * multiple threads accessing this concurrently may actually read + * these values at the same time + * - chunks can then be read in any order as preferred by the + * application + * + * While this should mean that the header will be read in 'stream' + * order (no seeks required), no guarantee is made beyond that to + * retrieve image/deep data in order. So if the backing file is + * truly a stream, it is up to the provider to implement appropriate + * caching of data to give the appearance of being able to seek/read + * atomically. + */ +read_func_ptr_t :: proc "c" ( + ctxt: const_context_t, + userdata: rawptr, + buffer: rawptr, + sz: u64, + offset: u64, + error_cb: stream_error_func_ptr_t) -> i64 + +/** Write custom function pointer + * + * Used to write data to a custom output. Expects similar semantics to + * pwrite or WriteFile with overlapped data under win32. + * + * It is required that this provides thread-safe concurrent access to + * the same file. While it is unlikely that multiple threads will + * be used to write data for compressed forms, it is possible. + * + * A file should be expected to be accessed in the following pattern: + * - upon open, the header and part information attributes is constructed. + * + * - when the write_header routine is called, the header becomes immutable + * and is written to the file. This computes the space to store the chunk + * offsets, but does not yet write the values. + * + * - Image chunks are written to the file, and appear in the order + * they are written, not in the ordering that is required by the + * chunk offset table (unless written in that order). This may vary + * slightly if the size of the chunks is not directly known and + * tight packing of data is necessary. + * + * - at file close, the chunk offset tables are written to the file. + */ +write_func_ptr_t :: proc "c" ( + ctxt: const_context_t, + userdata: rawptr, + buffer: rawptr, + sz: u64, + offset: u64, + error_cb: stream_error_func_ptr_t) -> i64 + +/** @brief Struct used to pass function pointers into the context + * initialization routines. + * + * This partly exists to avoid the chicken and egg issue around + * creating the storage needed for the context on systems which want + * to override the malloc/free routines. + * + * However, it also serves to make a tidier/simpler set of functions + * to create and start processing exr files. + * + * The size member is required for version portability. + * + * It can be initialized using \c EXR_DEFAULT_CONTEXT_INITIALIZER. + * + * \code{.c} + * exr_context_initializer_t myctxtinit = DEFAULT_CONTEXT_INITIALIZER; + * myctxtinit.error_cb = &my_super_cool_error_callback_function; + * ... + * \endcode + * + */ +context_initializer_t :: struct { + /** @brief Size member to tag initializer for version stability. + * + * This should be initialized to the size of the current + * structure. This allows EXR to add functions or other + * initializers in the future, and retain version compatibility + */ + size: c.size_t, + + /** @brief Error callback function pointer + * + * The error callback is allowed to be `NULL`, and will use a + * default print which outputs to \c stderr. + * + * @sa exr_error_handler_cb_t + */ + error_handler_fn: error_handler_cb_t, + + /** Custom allocator, if `NULL`, will use malloc. @sa memory_allocation_func_t */ + alloc_fn: memory_allocation_func_t, + + /** Custom deallocator, if `NULL`, will use free. @sa memory_free_func_t */ + free_fn: memory_free_func_t, + + /** Blind data passed to custom read, size, write, destroy + * functions below. Up to user to manage this pointer. + */ + user_data: rawptr, + + /** @brief Custom read routine. + * + * This is only used during read or update contexts. If this is + * provided, it is expected that the caller has previously made + * the stream available, and placed whatever stream/file data + * into \c user_data above. + * + * If this is `NULL`, and the context requested is for reading an + * exr file, an internal implementation is provided for reading + * from normal filesystem files, and the filename provided is + * attempted to be opened as such. + * + * Expected to be `NULL` for a write-only operation, but is ignored + * if it is provided. + * + * For update contexts, both read and write functions must be + * provided if either is. + * + * @sa exr_read_func_ptr_t + */ + read_fn: read_func_ptr_t, + + /** @brief Custom size query routine. + * + * Used to provide validation when reading header values. If this + * is not provided, but a custom read routine is provided, this + * will disable some of the validation checks when parsing the + * image header. + * + * Expected to be `NULL` for a write-only operation, but is ignored + * if it is provided. + * + * @sa exr_query_size_func_ptr_t + */ + size_fn: query_size_func_ptr_t, + + /** @brief Custom write routine. + * + * This is only used during write or update contexts. If this is + * provided, it is expected that the caller has previously made + * the stream available, and placed whatever stream/file data + * into \c user_data above. + * + * If this is `NULL`, and the context requested is for writing an + * exr file, an internal implementation is provided for reading + * from normal filesystem files, and the filename provided is + * attempted to be opened as such. + * + * For update contexts, both read and write functions must be + * provided if either is. + * + * @sa exr_write_func_ptr_t + */ + write_fn: write_func_ptr_t, + + /** @brief Optional function to destroy the user data block of a custom stream. + * + * Allows one to free any user allocated data, and close any handles. + * + * @sa exr_destroy_stream_func_ptr_t + * */ + destroy_fn: destroy_stream_func_ptr_t, + + /** Initialize a field specifying what the maximum image width + * allowed by the context is. See exr_set_default_maximum_image_size() to + * understand how this interacts with global defaults. + */ + max_image_width: c.int, + + /** Initialize a field specifying what the maximum image height + * allowed by the context is. See exr_set_default_maximum_image_size() to + * understand how this interacts with global defaults. + */ + max_image_height: c.int, + + /** Initialize a field specifying what the maximum tile width + * allowed by the context is. See exr_set_default_maximum_tile_size() to + * understand how this interacts with global defaults. + */ + max_tile_width: c.int, + + /** Initialize a field specifying what the maximum tile height + * allowed by the context is. See exr_set_default_maximum_tile_size() to + * understand how this interacts with global defaults. + */ + max_tile_height: c.int, + + /** Initialize a field specifying what the default zip compression level should be + * for this context. See exr_set_default_zip_compresion_level() to + * set it for all contexts. + */ + zip_level: c.int, + + /** Initialize the default dwa compression quality. See + * exr_set_default_dwa_compression_quality() to set the default + * for all contexts. + */ + dwa_quality: f32, + + /** Initialize with a bitwise or of the various context flags + */ + flags: c.int, +} + +/** @brief context flag which will enforce strict header validation + * checks and may prevent reading of files which could otherwise be + * processed. + */ +CONTEXT_FLAG_STRICT_HEADER :: (1 << 0) + +/** @brief Disables error messages while parsing headers + * + * The return values will remain the same, but error reporting will be + * skipped. This is only valid for reading contexts + */ +CONTEXT_FLAG_SILENT_HEADER_PARSE :: (1 << 1) + +/** @brief Disables reconstruction logic upon corrupt / missing data chunks + * + * This will disable the reconstruction logic that searches through an + * incomplete file, and will instead just return errors at read + * time. This is only valid for reading contexts + */ +CONTEXT_FLAG_DISABLE_CHUNK_RECONSTRUCTION :: (1 << 2) + +/** @brief Simple macro to initialize the context initializer with default values. */ +DEFAULT_CONTEXT_INITIALIZER :: context_initializer_t{zip_level = -2, dwa_quality = -1} + +/** @} */ /* context function pointer declarations */ + + +/** @brief Enum describing how default files are handled during write. */ +default_write_mode_t :: enum c.int { + WRITE_FILE_DIRECTLY = 0, /**< Overwrite filename provided directly, deleted upon error. */ + INTERMEDIATE_TEMP_FILE = 1, /**< Create a temporary file, renaming it upon successful write, leaving original upon error */ +} + + +@(link_prefix="exr_", default_calling_convention="c") +foreign lib { + /** @brief Check the magic number of the file and report + * `EXR_ERR_SUCCESS` if the file appears to be a valid file (or at least + * has the correct magic number and can be read). + */ + test_file_header :: proc(filename: cstring, ctxtdata: ^context_initializer_t) -> result_t --- + + /** @brief Close and free any internally allocated memory, + * calling any provided destroy function for custom streams. + * + * If the file was opened for write, first save the chunk offsets + * or any other unwritten data. + */ + finish :: proc(ctxt: ^context_t) -> result_t --- + + /** @brief Create and initialize a read-only exr read context. + * + * If a custom read function is provided, the filename is for + * informational purposes only, the system assumes the user has + * previously opened a stream, file, or whatever and placed relevant + * data in userdata to access that. + * + * One notable attribute of the context is that once it has been + * created and returned a successful code, it has parsed all the + * header data. This is done as one step such that it is easier to + * provide a safe context for multiple threads to request data from + * the same context concurrently. + * + * Once finished reading data, use exr_finish() to clean up + * the context. + * + * If you have custom I/O requirements, see the initializer context + * documentation \ref exr_context_initializer_t. The @p ctxtdata parameter + * is optional, if `NULL`, default values will be used. + */ + start_read :: proc( + ctxt: ^context_t, + filename: cstring, + ctxtdata: ^context_initializer_t) -> result_t --- + + /** @brief Create and initialize a write-only context. + * + * If a custom write function is provided, the filename is for + * informational purposes only, and the @p default_mode parameter will be + * ignored. As such, the system assumes the user has previously opened + * a stream, file, or whatever and placed relevant data in userdata to + * access that. + * + * Multi-Threading: To avoid issues with creating multi-part EXR + * files, the library approaches writing as a multi-step process, so + * the same concurrent guarantees can not be made for writing a + * file. The steps are: + * + * 1. Context creation (this function) + * + * 2. Part definition (required attributes and additional metadata) + * + * 3. Transition to writing data (this "commits" the part definitions, + * any changes requested after will result in an error) + * + * 4. Write part data in sequential order of parts (part0 + * -> partN-1). + * + * 5. Within each part, multiple threads can be encoding and writing + * data concurrently. For some EXR part definitions, this may be able + * to write data concurrently when it can predict the chunk sizes, or + * data is allowed to be padded. For others, it may need to + * temporarily cache chunks until the data is received to flush in + * order. The concurrency around this is handled by the library + * + * 6. Once finished writing data, use exr_finish() to clean + * up the context, which will flush any unwritten data such as the + * final chunk offset tables, and handle the temporary file flags. + * + * If you have custom I/O requirements, see the initializer context + * documentation \ref exr_context_initializer_t. The @p ctxtdata + * parameter is optional, if `NULL`, default values will be used. + */ + start_write :: proc( + ctxt: ^context_t, + filename: cstring, + default_mode: default_write_mode_t, + ctxtdata: ^context_initializer_t) -> result_t --- + + /** @brief Create a new context for updating an exr file in place. + * + * This is a custom mode that allows one to modify the value of a + * metadata entry, although not to change the size of the header, or + * any of the image data. + * + * If you have custom I/O requirements, see the initializer context + * documentation \ref exr_context_initializer_t. The @p ctxtdata parameter + * is optional, if `NULL`, default values will be used. + */ + start_inplace_header_update :: proc( + ctxt: ^context_t, + filename: cstring, + ctxtdata: ^context_initializer_t) -> result_t --- + + /** @brief Retrieve the file name the context is for as provided + * during the start routine. + * + * Do not free the resulting string. + */ + + get_file_name :: proc(ctxt: const_context_t, name: ^cstring) -> result_t --- + + /** @brief Query the user data the context was constructed with. This + * is perhaps useful in the error handler callback to jump back into + * an object the user controls. + */ + + get_user_data :: proc(ctxt: const_context_t, userdata: ^rawptr) -> result_t --- + + /** Any opaque attribute data entry of the specified type is tagged + * with these functions enabling downstream users to unpack (or pack) + * the data. + * + * The library handles the memory packed data internally, but the + * handler is expected to allocate and manage memory for the + * *unpacked* buffer (the library will call the destroy function). + * + * NB: the pack function will be called twice (unless there is a + * memory failure), the first with a `NULL` buffer, requesting the + * maximum size (or exact size if known) for the packed buffer, then + * the second to fill the output packed buffer, at which point the + * size can be re-updated to have the final, precise size to put into + * the file. + */ + register_attr_type_handler :: proc( + ctxt: context_t, + type: cstring, + unpack_func_ptr: proc "c" ( + ctxt: context_t, + data: rawptr, + attrsize: i32, + outsize: ^i32, + outbuffer: ^rawptr) -> result_t, + pack_func_ptr: proc "c" ( + ctxt: context_t, + data: rawptr, + datasize: i32, + outsize: ^i32, + outbuffer: rawptr) -> result_t, + destroy_unpacked_func_ptr: proc "c" ( + ctxt: context_t, data: rawptr, datasize: i32), + ) -> result_t --- + + /** @brief Enable long name support in the output context */ + + set_longname_support :: proc(ctxt: context_t, onoff: b32) -> result_t --- + + /** @brief Write the header data. + * + * Opening a new output file has a small initialization state problem + * compared to opening for read/update: we need to enable the user + * to specify an arbitrary set of metadata across an arbitrary number + * of parts. To avoid having to create the list of parts and entire + * metadata up front, prior to calling the above exr_start_write(), + * allow the data to be set, then once this is called, it switches + * into a mode where the library assumes the data is now valid. + * + * It will recompute the number of chunks that will be written, and + * reset the chunk offsets. If you modify file attributes or part + * information after a call to this, it will error. + */ + write_header :: proc(ctxt: context_t) -> result_t --- +} \ No newline at end of file diff --git a/vendor/OpenEXRCore/exr_debug.odin b/vendor/OpenEXRCore/exr_debug.odin new file mode 100644 index 000000000..e376e9ddd --- /dev/null +++ b/vendor/OpenEXRCore/exr_debug.odin @@ -0,0 +1,12 @@ +package vendor_openexr + +when ODIN_OS == .Windows { + foreign import lib "OpenEXRCore-3_1.lib" +} else { + foreign import lib "system:OpenEXRCore-3_1" +} + +@(link_prefix="exr_", default_calling_convention="c") +foreign lib { + print_context_info :: proc(c: const_context_t, verbose: b32) -> result_t --- +} \ No newline at end of file diff --git a/vendor/OpenEXRCore/exr_decode.odin b/vendor/OpenEXRCore/exr_decode.odin new file mode 100644 index 000000000..2065ee44d --- /dev/null +++ b/vendor/OpenEXRCore/exr_decode.odin @@ -0,0 +1,292 @@ +package vendor_openexr + +when ODIN_OS == .Windows { + foreign import lib "OpenEXRCore-3_1.lib" +} else { + foreign import lib "system:OpenEXRCore-3_1" +} + +import "core:c" + +/** Can be bit-wise or'ed into the decode_flags in the decode pipeline. + * + * Indicates that the sample count table should be decoded to a an + * individual sample count list (n, m, o, ...), with an extra int at + * the end containing the total samples. + * + * Without this (i.e. a value of 0 in that bit), indicates the sample + * count table should be decoded to a cumulative list (n, n+m, n+m+o, + * ...), which is the on-disk representation. + */ +DECODE_SAMPLE_COUNTS_AS_INDIVIDUAL :: u16(1 << 0) + +/** Can be bit-wise or'ed into the decode_flags in the decode pipeline. + * + * Indicates that the data in the channel pointers to decode to is not + * a direct pointer, but instead is a pointer-to-pointers. In this + * mode, the user_pixel_stride and user_line_stride are used to + * advance the pointer offsets for each pixel in the output, but the + * user_bytes_per_element and user_data_type are used to put + * (successive) entries into each destination pointer (if not `NULL`). + * + * So each channel pointer must then point to an array of + * chunk.width * chunk.height pointers. + * + * With this, you can only extract desired pixels (although all the + * pixels must be initially decompressed) to handle such operations + * like proxying where you might want to read every other pixel. + * + * If this is NOT set (0), the default unpacking routine assumes the + * data will be planar and contiguous (each channel is a separate + * memory block), ignoring user_line_stride and user_pixel_stride. + */ +DECODE_NON_IMAGE_DATA_AS_POINTERS :: u16(1 << 1) + +/** + * When reading non-image data (i.e. deep), only read the sample table. + */ +DECODE_SAMPLE_DATA_ONLY :: u16(1 << 2) + +/** + * Struct meant to be used on a per-thread basis for reading exr data + * + * As should be obvious, this structure is NOT thread safe, but rather + * meant to be used by separate threads, which can all be accessing + * the same context concurrently. + */ +decode_pipeline_t :: struct { + /** The output channel information for this chunk. + * + * User is expected to fill the channel pointers for the desired + * output channels (any that are `NULL` will be skipped) if you are + * going to use exr_decoding_choose_default_routines(). If all that is + * desired is to read and decompress the data, this can be left + * uninitialized. + * + * Describes the channel information. This information is + * allocated dynamically during exr_decoding_initialize(). + */ + channels: [^]coding_channel_info_t, + channel_count: i16, + + /** Decode flags to control the behavior. */ + decode_flags: u16, + + /** Copy of the parameters given to the initialize/update for + * convenience. + */ + part_index: c.int, + ctx: const_context_t, + chunk: chunk_info_t, + + /** Can be used by the user to pass custom context data through + * the decode pipeline. + */ + decoding_user_data: rawptr, + + /** The (compressed) buffer. + * + * If `NULL`, will be allocated during the run of the pipeline. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + packed_buffer: rawptr, + + /** Used when re-using the same decode pipeline struct to know if + * chunk is changed size whether current buffer is large enough. + */ + packed_alloc_size: c.size_t, + + /** The decompressed buffer (unpacked_size from the chunk block + * info), but still packed into storage order, only needed for + * compressed files. + * + * If `NULL`, will be allocated during the run of the pipeline when + * needed. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + unpacked_buffer: rawptr, + + /** Used when re-using the same decode pipeline struct to know if + * chunk is changed size whether current buffer is large enough. + */ + unpacked_alloc_size: c.size_t, + + /** For deep or other non-image data: packed sample table + * (compressed, raw on disk representation). + */ + packed_sample_count_table: rawptr, + packed_sample_count_alloc_size: c.size_t, + + /** Usable, native sample count table. Depending on the flag set + * above, will be decoded to either a cumulative list (n, n+m, + * n+m+o, ...), or an individual table (n, m, o, ...). As an + * optimization, if the latter individual count table is chosen, + * an extra int32_t will be allocated at the end of the table to + * contain the total count of samples, so the table will be n+1 + * samples in size. + */ + sample_count_table: [^]i32, + sample_count_alloc_size: c.size_t, + + /** A scratch buffer of unpacked_size for intermediate results. + * + * If `NULL`, will be allocated during the run of the pipeline when + * needed. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + scratch_buffer_1: rawptr, + + /** Used when re-using the same decode pipeline struct to know if + * chunk is changed size whether current buffer is large enough. + */ + scratch_alloc_size_1: c.size_t, + + /** Some decompression routines may need a second scratch buffer (zlib). + * + * If `NULL`, will be allocated during the run of the pipeline when + * needed. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + scratch_buffer_2: rawptr, + + /** Used when re-using the same decode pipeline struct to know if + * chunk is changed size whether current buffer is large enough. + */ + scratch_alloc_size_2: c.size_t, + + /** Enable a custom allocator for the different buffers (if + * decoding on a GPU). If `NULL`, will use the allocator from the + * context. + */ + alloc_fn: proc "c" (transcoding_pipeline_buffer_id_t, c.size_t) -> rawptr, + + /** Enable a custom allocator for the different buffers (if + * decoding on a GPU). If `NULL`, will use the allocator from the + * context. + */ + free_fn: proc "c" (transcoding_pipeline_buffer_id_t, rawptr), + + /** Function chosen to read chunk data from the context. + * + * Initialized to a default generic read routine, may be updated + * based on channel information when + * exr_decoding_choose_default_routines() is called. This is done such that + * if the file is uncompressed and the output channel data is + * planar and the same type, the read function can read straight + * into the output channels, getting closer to a zero-copy + * operation. Otherwise a more traditional read, decompress, then + * unpack pipeline will be used with a default reader. + * + * This is allowed to be overridden, but probably is not necessary + * in most scenarios. + */ + read_fn: proc "c" (pipeline: ^decode_pipeline_t) -> result_t, + + /** Function chosen based on the compression type of the part to + * decompress data. + * + * If the user has a custom decompression method for the + * compression on this part, this can be changed after + * initialization. + * + * If only compressed data is desired, then assign this to `NULL` + * after initialization. + */ + decompress_fn: proc "c" (pipeline: ^decode_pipeline_t) -> result_t, + + /** Function which can be provided if you have bespoke handling for + * non-image data and need to re-allocate the data to handle the + * about-to-be unpacked data. + * + * If left `NULL`, will assume the memory pointed to by the channel + * pointers is sufficient. + */ + realloc_nonimage_data_fn: proc "c" (pipeline: ^decode_pipeline_t) -> result_t, + + /** Function chosen based on the output layout of the channels of the part to + * decompress data. + * + * This will be `NULL` after initialization, until the user + * specifies a custom routine, or initializes the channel data and + * calls exr_decoding_choose_default_routines(). + * + * If only compressed data is desired, then leave or assign this + * to `NULL` after initialization. + */ + unpack_and_convert_fn: proc "c" (pipeline: ^decode_pipeline_t) -> result_t, + + /** Small stash of channel info values. This is faster than calling + * malloc when the channel count in the part is small (RGBAZ), + * which is super common, however if there are a large number of + * channels, it will allocate space for that, so do not rely on + * this being used. + */ + _quick_chan_store: [5]coding_channel_info_t, +} + +DECODE_PIPELINE_INITIALIZER :: decode_pipeline_t{} + + +@(link_prefix="exr_", default_calling_convention="c") +foreign lib { + /** Initialize the decoding pipeline structure with the channel info + * for the specified part, and the first block to be read. + * + * NB: The decode->unpack_and_convert_fn field will be `NULL` after this. If that + * stage is desired, initialize the channel output information and + * call exr_decoding_choose_default_routines(). + */ + decoding_initialize :: proc( + ctxt: const_context_t, + part_index: c.int, + cinfo: ^chunk_info_t, + decode: ^decode_pipeline_t) -> result_t --- + + /** Given an initialized decode pipeline, find appropriate functions + * to read and shuffle/convert data into the defined channel outputs. + * + * Calling this is not required if custom routines will be used, or if + * just the raw compressed data is desired. Although in that scenario, + * it is probably easier to just read the chunk directly using + * exr_read_chunk(). + */ + decoding_choose_default_routines :: proc( + ctxt: const_context_t, part_index: c.int, decode: ^decode_pipeline_t) -> result_t --- + + /** Given a decode pipeline previously initialized, update it for the + * new chunk to be read. + * + * In this manner, memory buffers can be re-used to avoid continual + * malloc/free calls. Further, it allows the previous choices for + * the various functions to be quickly re-used. + */ + decoding_update :: proc( + ctxt: const_context_t, + part_index: c.int, + cinfo: ^chunk_info_t, + decode: ^decode_pipeline_t) -> result_t --- + + /** Execute the decoding pipeline. */ + decoding_run :: proc( + ctxt: const_context_t, part_index: c.int, decode: ^decode_pipeline_t) -> result_t --- + + /** Free any intermediate memory in the decoding pipeline. + * + * This does *not* free any pointers referred to in the channel info + * areas, but rather only the intermediate buffers and memory needed + * for the structure itself. + */ + decoding_destroy :: proc(ctxt: const_context_t, decode: ^decode_pipeline_t) -> result_t --- +} \ No newline at end of file diff --git a/vendor/OpenEXRCore/exr_encode.odin b/vendor/OpenEXRCore/exr_encode.odin new file mode 100644 index 000000000..9d9e80c22 --- /dev/null +++ b/vendor/OpenEXRCore/exr_encode.odin @@ -0,0 +1,323 @@ +package vendor_openexr + +when ODIN_OS == .Windows { + foreign import lib "OpenEXRCore-3_1.lib" +} else { + foreign import lib "system:OpenEXRCore-3_1" +} + +import "core:c" + +/** Can be bit-wise or'ed into the decode_flags in the decode pipeline. + * + * Indicates that the sample count table should be encoded from an + * individual sample count list (n, m, o, ...), meaning it will have + * to compute the cumulative counts on the fly. + * + * Without this (i.e. a value of 0 in that bit), indicates the sample + * count table is already a cumulative list (n, n+m, n+m+o, ...), + * which is the on-disk representation. + */ +ENCODE_DATA_SAMPLE_COUNTS_ARE_INDIVIDUAL :: u16(1 << 0) + +/** Can be bit-wise or'ed into the decode_flags in the decode pipeline. + * + * Indicates that the data in the channel pointers to encode from is not + * a direct pointer, but instead is a pointer-to-pointers. In this + * mode, the user_pixel_stride and user_line_stride are used to + * advance the pointer offsets for each pixel in the output, but the + * user_bytes_per_element and user_data_type are used to put + * (successive) entries into each destination. + * + * So each channel pointer must then point to an array of + * chunk.width * chunk.height pointers. If an entry is + * `NULL`, 0 samples will be placed in the output. + * + * If this is NOT set (0), the default packing routine assumes the + * data will be planar and contiguous (each channel is a separate + * memory block), ignoring user_line_stride and user_pixel_stride and + * advancing only by the sample counts and bytes per element. + */ +ENCODE_NON_IMAGE_DATA_AS_POINTERS :: u16(1 << 1) + +/** Struct meant to be used on a per-thread basis for writing exr data. + * + * As should be obvious, this structure is NOT thread safe, but rather + * meant to be used by separate threads, which can all be accessing + * the same context concurrently. + */ + encode_pipeline_t :: struct { + /** The output channel information for this chunk. + * + * User is expected to fill the channel pointers for the input + * channels. For writing, all channels must be initialized prior + * to using exr_encoding_choose_default_routines(). If a custom pack routine + * is written, that is up to the implementor. + * + * Describes the channel information. This information is + * allocated dynamically during exr_encoding_initialize(). + */ + channels: [^]coding_channel_info_t, + channel_count: i16, + + /** Encode flags to control the behavior. */ + encode_flags: u16, + + /** Copy of the parameters given to the initialize/update for convenience. */ + part_index: c.int, + ctx: const_context_t, + chunk: chunk_info_t, + + /** Can be used by the user to pass custom context data through + * the encode pipeline. + */ + encoding_user_data: rawptr, + + /** The packed buffer where individual channels have been put into here. + * + * If `NULL`, will be allocated during the run of the pipeline. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + packed_buffer: rawptr, + + /** Differing from the allocation size, the number of actual bytes */ + packed_bytes: u64, + + /** Used when re-using the same encode pipeline struct to know if + * chunk is changed size whether current buffer is large enough + * + * If `NULL`, will be allocated during the run of the pipeline. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + packed_alloc_size: c.size_t, + + /** For deep data. NB: the members NOT const because we need to + * temporarily swap it to xdr order and restore it (to avoid a + * duplicate buffer allocation). + * + * Depending on the flag set above, will be treated either as a + * cumulative list (n, n+m, n+m+o, ...), or an individual table + * (n, m, o, ...). */ + sample_count_table: [^]i32, + + /** Allocated table size (to avoid re-allocations). Number of + * samples must always be width * height for the chunk. + */ + sample_count_alloc_size: c.size_t, + + /** Packed sample table (compressed, raw on disk representation) + * for deep or other non-image data. + */ + packed_sample_count_table: rawptr, + + /** Number of bytes to write (actual size) for the + * packed_sample_count_table. + */ + packed_sample_count_bytes: c.size_t, + + /** Allocated size (to avoid re-allocations) for the + * packed_sample_count_table. + */ + packed_sample_count_alloc_size: c.size_t, + + /** The compressed buffer, only needed for compressed files. + * + * If `NULL`, will be allocated during the run of the pipeline when + * needed. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + compressed_buffer: rawptr, + + /** Must be filled in as the pipeline runs to inform the writing + * software about the compressed size of the chunk (if it is an + * uncompressed file or the compression would make the file + * larger, it is expected to be the packed_buffer) + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to zero here. Be cognizant of any + * custom allocators. + */ + compressed_bytes: c.size_t, + + /** Used when re-using the same encode pipeline struct to know if + * chunk is changed size whether current buffer is large enough. + * + * If `NULL`, will be allocated during the run of the pipeline when + * needed. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to zero here. Be cognizant of any + * custom allocators. + */ + compressed_alloc_size: c.size_t, + + /** A scratch buffer for intermediate results. + * + * If `NULL`, will be allocated during the run of the pipeline when + * needed. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + scratch_buffer_1: rawptr, + + /** Used when re-using the same encode pipeline struct to know if + * chunk is changed size whether current buffer is large enough. + * + * If `NULL`, will be allocated during the run of the pipeline when + * needed. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + scratch_alloc_size_1: c.size_t, + + /** Some compression routines may need a second scratch buffer. + * + * If `NULL`, will be allocated during the run of the pipeline when + * needed. + * + * If the caller wishes to take control of the buffer, simple + * adopt the pointer and set it to `NULL` here. Be cognizant of any + * custom allocators. + */ + scratch_buffer_2: rawptr, + + /** Used when re-using the same encode pipeline struct to know if + * chunk is changed size whether current buffer is large enough. + */ + scratch_alloc_size_2: c.size_t, + + /** Enable a custom allocator for the different buffers (if + * encoding on a GPU). If `NULL`, will use the allocator from the + * context. + */ + alloc_fn: proc "c" (transcoding_pipeline_buffer_id_t, c.size_t) -> rawptr, + + /** Enable a custom allocator for the different buffers (if + * encoding on a GPU). If `NULL`, will use the allocator from the + * context. + */ + free_fn: proc "c" (transcoding_pipeline_buffer_id_t, rawptr), + + /** Function chosen based on the output layout of the channels of the part to + * decompress data. + * + * If the user has a custom method for the + * compression on this part, this can be changed after + * initialization. + */ + convert_and_pack_fn: proc "c" (pipeline: ^encode_pipeline_t) -> result_t, + + /** Function chosen based on the compression type of the part to + * compress data. + * + * If the user has a custom compression method for the compression + * type on this part, this can be changed after initialization. + */ + compress_fn: proc "c" (pipeline: ^encode_pipeline_t) -> result_t, + + /** This routine is used when waiting for other threads to finish + * writing previous chunks such that this thread can write this + * chunk. This is used for parts which have a specified chunk + * ordering (increasing/decreasing y) and the chunks can not be + * written randomly (as could be true for uncompressed). + * + * This enables the calling application to contribute thread time + * to other computation as needed, or just use something like + * pthread_yield(). + * + * By default, this routine will be assigned to a function which + * returns an error, failing the encode immediately. In this way, + * it assumes that there is only one thread being used for + * writing. + * + * It is up to the user to provide an appropriate routine if + * performing multi-threaded writing. + */ + yield_until_ready_fn: proc "c" (pipeline: ^encode_pipeline_t) -> result_t, + + /** Function chosen to write chunk data to the context. + * + * This is allowed to be overridden, but probably is not necessary + * in most scenarios. + */ + write_fn: proc "c" (pipeline: ^encode_pipeline_t) -> result_t, + + /** Small stash of channel info values. This is faster than calling + * malloc when the channel count in the part is small (RGBAZ), + * which is super common, however if there are a large number of + * channels, it will allocate space for that, so do not rely on + * this being used. + */ + _quick_chan_store: [5]coding_channel_info_t, +} + +ENCODE_PIPELINE_INITIALIZER :: encode_pipeline_t{} + + +@(link_prefix="exr_", default_calling_convention="c") +foreign lib { + /** Initialize the encoding pipeline structure with the channel info + * for the specified part based on the chunk to be written. + * + * NB: The encode_pipe->pack_and_convert_fn field will be `NULL` after this. If that + * stage is desired, initialize the channel output information and + * call exr_encoding_choose_default_routines(). + */ + encoding_initialize :: proc( + ctxt: const_context_t, + part_index: c.int, + cinfo: ^chunk_info_t, + encode_pipe: ^encode_pipeline_t) -> result_t --- + + /** Given an initialized encode pipeline, find an appropriate + * function to shuffle and convert data into the defined channel + * outputs. + * + * Calling this is not required if a custom routine will be used, or + * if just the raw decompressed data is desired. + */ + encoding_choose_default_routines :: proc( + ctxt: const_context_t, + part_index: c.int, + encode_pipe: ^encode_pipeline_t) -> result_t --- + + /** Given a encode pipeline previously initialized, update it for the + * new chunk to be written. + * + * In this manner, memory buffers can be re-used to avoid continual + * malloc/free calls. Further, it allows the previous choices for + * the various functions to be quickly re-used. + */ + encoding_update :: proc( + ctxt: const_context_t, + part_index: c.int, + cinfo: ^chunk_info_t, + encode_pipe: ^encode_pipeline_t) -> result_t --- + + /** Execute the encoding pipeline. */ + encoding_run :: proc( + ctxt: const_context_t, + part_index: c.int, + encode_pipe: ^encode_pipeline_t) -> result_t --- + + /** Free any intermediate memory in the encoding pipeline. + * + * This does NOT free any pointers referred to in the channel info + * areas, but rather only the intermediate buffers and memory needed + * for the structure itself. + */ + encoding_destroy :: proc(ctxt: const_context_t, encode_pipe: ^encode_pipeline_t) -> result_t --- +} \ No newline at end of file diff --git a/vendor/OpenEXRCore/exr_errors.odin b/vendor/OpenEXRCore/exr_errors.odin new file mode 100644 index 000000000..092b888dc --- /dev/null +++ b/vendor/OpenEXRCore/exr_errors.odin @@ -0,0 +1,67 @@ +package vendor_openexr + +when ODIN_OS == .Windows { + foreign import lib "OpenEXRCore-3_1.lib" +} else { + foreign import lib "system:OpenEXRCore-3_1" +} + +import "core:c" + +#assert(size_of(c.int) == size_of(i32)) + +/** Error codes that may be returned by various functions. */ +/** Return type for all functions. */ +result_t :: enum i32 { + SUCCESS = 0, + OUT_OF_MEMORY, + MISSING_CONTEXT_ARG, + INVALID_ARGUMENT, + ARGUMENT_OUT_OF_RANGE, + FILE_ACCESS, + FILE_BAD_HEADER, + NOT_OPEN_READ, + NOT_OPEN_WRITE, + HEADER_NOT_WRITTEN, + READ_IO, + WRITE_IO, + NAME_TOO_LONG, + MISSING_REQ_ATTR, + INVALID_ATTR, + NO_ATTR_BY_NAME, + ATTR_TYPE_MISMATCH, + ATTR_SIZE_MISMATCH, + SCAN_TILE_MIXEDAPI, + TILE_SCAN_MIXEDAPI, + MODIFY_SIZE_CHANGE, + ALREADY_WROTE_ATTRS, + BAD_CHUNK_LEADER, + CORRUPT_CHUNK, + INCORRECT_PART, + INCORRECT_CHUNK, + USE_SCAN_DEEP_WRITE, + USE_TILE_DEEP_WRITE, + USE_SCAN_NONDEEP_WRITE, + USE_TILE_NONDEEP_WRITE, + INVALID_SAMPLE_DATA, + FEATURE_NOT_IMPLEMENTED, + UNKNOWN, +} + +error_code_t :: result_t + + +@(link_prefix="exr_", default_calling_convention="c") +foreign lib { + /** @brief Return a static string corresponding to the specified error code. + * + * The string should not be freed (it is compiled into the binary). + */ + get_default_error_message :: proc(code: result_t) -> cstring --- + + /** @brief Return a static string corresponding to the specified error code. + * + * The string should not be freed (it is compiled into the binary). + */ + get_error_code_as_string :: proc(code: result_t) -> cstring --- +} diff --git a/vendor/OpenEXRCore/exr_part.odin b/vendor/OpenEXRCore/exr_part.odin new file mode 100644 index 000000000..7d7530e50 --- /dev/null +++ b/vendor/OpenEXRCore/exr_part.odin @@ -0,0 +1,737 @@ +package vendor_openexr + +when ODIN_OS == .Windows { + foreign import lib "OpenEXRCore-3_1.lib" +} else { + foreign import lib "system:OpenEXRCore-3_1" +} + +import "core:c" + +attr_list_access_mode_t :: enum c.int { + FILE_ORDER, /**< Order they appear in the file */ + SORTED_ORDER, /**< Alphabetically sorted */ +} + +@(link_prefix="exr_", default_calling_convention="c") +foreign lib { + /** @brief Query how many parts are in the file. */ + get_count :: proc (ctxt: const_context_t, count: ^c.int) -> result_t --- + + /** @brief Query the part name for the specified part. + * + * NB: If this file is a single part file and name has not been set, this + * will return `NULL`. + */ + get_name :: proc(ctxt: const_context_t, part_index: c.int, out: ^cstring) -> result_t --- + + /** @brief Query the storage type for the specified part. */ + get_storage :: proc(ctxt: const_context_t, part_index: c.int, out: ^storage_t) -> result_t --- + + /** @brief Define a new part in the file. */ + add_part :: proc( + ctxt: context_t, + partname: rawptr, + type: storage_t, + new_index: ^c.int) -> result_t --- + + /** @brief Query how many levels are in the specified part. + * + * If the part is a tiled part, fill in how many tile levels are present. + * + * Return `ERR_SUCCESS` on success, an error otherwise (i.e. if the part + * is not tiled). + * + * It is valid to pass `NULL` to either of the @p levelsx or @p levelsy + * arguments, which enables testing if this part is a tiled part, or + * if you don't need both (i.e. in the case of a mip-level tiled + * image) + */ + get_tile_levels :: proc( + ctxt: const_context_t, + part_index: c.int, + levelsx: ^i32, + levelsy: ^i32) -> result_t --- + + /** @brief Query the tile size for a particular level in the specified part. + * + * If the part is a tiled part, fill in the tile size for the + * specified part/level. + * + * Return `ERR_SUCCESS` on success, an error otherwise (i.e. if the + * part is not tiled). + * + * It is valid to pass `NULL` to either of the @p tilew or @p tileh + * arguments, which enables testing if this part is a tiled part, or + * if you don't need both (i.e. in the case of a mip-level tiled + * image) + */ + get_tile_sizes :: proc( + ctxt: const_context_t, + part_index: c.int, + levelx: c.int, + levely: c.int, + tilew: ^i32, + tileh: ^i32) -> result_t --- + + /** @brief Query the data sizes for a particular level in the specified part. + * + * If the part is a tiled part, fill in the width/height for the + * specified levels. + * + * Return `ERR_SUCCESS` on success, an error otherwise (i.e. if the part + * is not tiled). + * + * It is valid to pass `NULL` to either of the @p levw or @p levh + * arguments, which enables testing if this part is a tiled part, or + * if you don't need both for some reason. + */ + get_level_sizes :: proc( + ctxt: const_context_t, + part_index: c.int, + levelx: c.int, + levely: c.int, + levw: ^i32, + levh: ^i32) -> result_t --- + + /** Return the number of chunks contained in this part of the file. + * + * As in the technical documentation for OpenEXR, the chunk is the + * generic term for a pixel data block. This is the atomic unit that + * this library uses to negotiate data to and from a context. + * + * This should be used as a basis for splitting up how a file is + * processed. Depending on the compression, a different number of + * scanlines are encoded in each chunk, and since those need to be + * encoded/decoded as a block, the chunk should be the basis for I/O + * as well. + */ + get_chunk_count :: proc(ctxt: const_context_t, part_index: c.int, out: ^i32) -> result_t --- + + /** Return the number of scanlines chunks for this file part. + * + * When iterating over a scanline file, this may be an easier metric + * for multi-threading or other access than only negotiating chunk + * counts, and so is provided as a utility. + */ + get_scanlines_per_chunk :: proc(ctxt: const_context_t, part_index: c.int, out: ^i32) -> result_t --- + + /** Return the maximum unpacked size of a chunk for the file part. + * + * This may be used ahead of any actual reading of data, so can be + * used to pre-allocate buffers for multiple threads in one block or + * whatever your application may require. + */ + get_chunk_unpacked_size :: proc(ctxt: const_context_t, part_index: c.int, out: ^u64) -> result_t --- + + /** @brief Retrieve the zip compression level used for the specified part. + * + * This only applies when the compression method involves using zip + * compression (zip, zips, some modes of DWAA/DWAB). + * + * This value is NOT persisted in the file, and only exists for the + * lifetime of the context, so will be at the default value when just + * reading a file. + */ + get_zip_compression_level :: proc(ctxt: const_context_t, part_index: c.int, level: ^c.int) -> result_t --- + + /** @brief Set the zip compression method used for the specified part. + * + * This only applies when the compression method involves using zip + * compression (zip, zips, some modes of DWAA/DWAB). + * + * This value is NOT persisted in the file, and only exists for the + * lifetime of the context, so this value will be ignored when + * reading a file. + */ + set_zip_compression_level :: proc(ctxt: context_t, part_index: c.int, level: c.int) -> result_t --- + + /** @brief Retrieve the dwa compression level used for the specified part. + * + * This only applies when the compression method is DWAA/DWAB. + * + * This value is NOT persisted in the file, and only exists for the + * lifetime of the context, so will be at the default value when just + * reading a file. + */ + get_dwa_compression_level :: proc(ctxt: const_context_t, part_index: c.int, level: ^f32) -> result_t --- + + /** @brief Set the dwa compression method used for the specified part. + * + * This only applies when the compression method is DWAA/DWAB. + * + * This value is NOT persisted in the file, and only exists for the + * lifetime of the context, so this value will be ignored when + * reading a file. + */ + set_dwa_compression_level :: proc(ctxt: context_t, part_index: c.int, level: f32) -> result_t --- + + /**************************************/ + + /** @defgroup PartMetadata Functions to get and set metadata for a particular part. + * @{ + * + */ + + /** @brief Query the count of attributes in a part. */ + get_attribute_count :: proc(ctxt: const_context_t, part_index: c.int, count: ^i32) -> result_t --- + + /** @brief Query a particular attribute by index. */ + get_attribute_by_index :: proc( + ctxt: const_context_t, + part_index: c.int, + mode: attr_list_access_mode_t, + idx: i32, + outattr: ^^attribute_t) -> result_t --- + + /** @brief Query a particular attribute by name. */ + get_attribute_by_name :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + outattr: ^^attribute_t) -> result_t --- + + /** @brief Query the list of attributes in a part. + * + * This retrieves a list of attributes currently defined in a part. + * + * If outlist is `NULL`, this function still succeeds, filling only the + * count. In this manner, the user can allocate memory for the list of + * attributes, then re-call this function to get the full list. + */ + get_attribute_list :: proc( + ctxt: const_context_t, + part_index: c.int, + mode: attr_list_access_mode_t, + count: ^i32, + outlist: ^[^]attribute_t) -> result_t --- + + /** Declare an attribute within the specified part. + * + * Only valid when a file is opened for write. + */ + attr_declare_by_type :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + type: cstring, + newattr: ^^attribute_t) -> result_t --- + + /** @brief Declare an attribute within the specified part. + * + * Only valid when a file is opened for write. + */ + attr_declare :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + type: attribute_type_t, + newattr: ^^attribute_t) -> result_t --- + + /** + * @defgroup RequiredAttributeHelpers Required Attribute Utililities + * + * @brief These are a group of functions for attributes that are + * required to be in every part of every file. + * + * @{ + */ + + /** @brief Initialize all required attributes for all files. + * + * NB: other file types do require other attributes, such as the tile + * description for a tiled file. + */ + initialize_required_attr :: proc( + ctxt: context_t, + part_index: c.int, + displayWindow: ^attr_box2i_t, + dataWindow: ^attr_box2i_t, + pixelaspectratio: f32, + screenWindowCenter: attr_v2f_t, + screenWindowWidth: f32, + lineorder: lineorder_t, + ctype: compression_t) -> result_t --- + + /** @brief Initialize all required attributes to default values: + * + * - `displayWindow` is set to (0, 0 -> @p width - 1, @p height - 1) + * - `dataWindow` is set to (0, 0 -> @p width - 1, @p height - 1) + * - `pixelAspectRatio` is set to 1.0 + * - `screenWindowCenter` is set to 0.f, 0.f + * - `screenWindowWidth` is set to 1.f + * - `lineorder` is set to `INCREASING_Y` + * - `compression` is set to @p ctype + */ + initialize_required_attr_simple :: proc( + ctxt: context_t, + part_index: c.int, + width: i32, + height: i32, + ctype: compression_t) -> result_t --- + + /** @brief Copy the attributes from one part to another. + * + * This allows one to quickly unassigned attributes from one source to another. + * + * If an attribute in the source part has not been yet set in the + * destination part, the item will be copied over. + * + * For example, when you add a part, the storage type and name + * attributes are required arguments to the definition of a new part, + * but channels has not yet been assigned. So by calling this with an + * input file as the source, you can copy the channel definitions (and + * any other unassigned attributes from the source). + */ + copy_unset_attributes :: proc( + ctxt: context_t, + part_index: c.int, + source: const_context_t, + src_part_index: c.int) -> result_t --- + + /** @brief Retrieve the list of channels. */ + get_channels :: proc(ctxt: const_context_t, part_index: c.int, chlist: ^^attr_chlist_t) -> result_t --- + + /** @brief Define a new channel to the output file part. + * + * The @p percept parameter is used for lossy compression techniques + * to indicate that the value represented is closer to linear (1) or + * closer to logarithmic (0). For r, g, b, luminance, this is normally + * 0. + */ + add_channel :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + ptype: pixel_type_t, + percept: perceptual_treatment_t, + xsamp: i32, + ysamp: i32) -> c.int --- + + /** @brief Copy the channels from another source. + * + * Useful if you are manually constructing the list or simply copying + * from an input file. + */ + set_channels :: proc(ctxt: context_t, part_index: c.int, channels: ^attr_chlist_t) -> result_t --- + + /** @brief Retrieve the compression method used for the specified part. */ + get_compression :: proc(ctxt: const_context_t, part_index: c.int, compression: ^compression_t) -> result_t --- + /** @brief Set the compression method used for the specified part. */ + set_compression :: proc(ctxt: context_t, part_index: c.int, ctype: compression_t) -> result_t --- + + /** @brief Retrieve the data window for the specified part. */ + get_data_window :: proc(ctxt: const_context_t, part_index: c.int, out: ^attr_box2i_t) -> result_t --- + /** @brief Set the data window for the specified part. */ + set_data_window :: proc(ctxt: context_t, part_index: c.int, dw: ^attr_box2i_t) -> c.int --- + + /** @brief Retrieve the display window for the specified part. */ + get_display_window :: proc(ctxt: const_context_t, part_index: c.int, out: ^attr_box2i_t) -> result_t --- + /** @brief Set the display window for the specified part. */ + set_display_window :: proc(ctxt: context_t, part_index: c.int, dw: ^attr_box2i_t) -> c.int --- + + /** @brief Retrieve the line order for storing data in the specified part (use 0 for single part images). */ + get_lineorder :: proc(ctxt: const_context_t, part_index: c.int, out: ^lineorder_t) -> result_t --- + /** @brief Set the line order for storing data in the specified part (use 0 for single part images). */ + set_lineorder :: proc(ctxt: context_t, part_index: c.int, lo: lineorder_t) -> result_t --- + + /** @brief Retrieve the pixel aspect ratio for the specified part (use 0 for single part images). */ + get_pixel_aspect_ratio :: proc(ctxt: const_context_t, part_index: c.int, par: ^f32) -> result_t --- + /** @brief Set the pixel aspect ratio for the specified part (use 0 for single part images). */ + set_pixel_aspect_ratio :: proc(ctxt: context_t, part_index: c.int, par: f32) -> result_t --- + + /** @brief Retrieve the screen oriented window center for the specified part (use 0 for single part images). */ + get_screen_window_center :: proc(ctxt: const_context_t, part_index: c.int, wc: ^attr_v2f_t) -> result_t --- + /** @brief Set the screen oriented window center for the specified part (use 0 for single part images). */ + set_screen_window_center :: proc(ctxt: context_t, part_index: c.int, wc: ^attr_v2f_t) -> c.int --- + + /** @brief Retrieve the screen oriented window width for the specified part (use 0 for single part images). */ + get_screen_window_width :: proc(ctxt: const_context_t, part_index: c.int, out: ^f32) -> result_t --- + /** @brief Set the screen oriented window width for the specified part (use 0 for single part images). */ + set_screen_window_width :: proc(ctxt: context_t, part_index: c.int, ssw: f32) -> result_t --- + + /** @brief Retrieve the tiling info for a tiled part (use 0 for single part images). */ + get_tile_descriptor :: proc( + ctxt: const_context_t, + part_index: c.int, + xsize: ^u32, + ysize: ^u32, + level: ^tile_level_mode_t, + round: ^tile_round_mode_t) -> result_t --- + + /** @brief Set the tiling info for a tiled part (use 0 for single part images). */ + set_tile_descriptor :: proc( + ctxt: context_t, + part_index: c.int, + x_size: u32, + y_size: u32, + level_mode: tile_level_mode_t, + round_mode: tile_round_mode_t) -> result_t --- + + set_name :: proc(ctxt: context_t, part_index: c.int, val: cstring) -> result_t --- + + get_version :: proc(ctxt: const_context_t, part_index: c.int, out: ^i32) -> result_t --- + + set_version :: proc(ctxt: context_t, part_index: c.int, val: i32) -> result_t --- + + set_chunk_count :: proc(ctxt: context_t, part_index: c.int, val: i32) -> result_t --- + + /** @} */ /* required attr group. */ + + /** + * @defgroup BuiltinAttributeHelpers Attribute utilities for builtin types + * + * @brief These are a group of functions for attributes that use the builtin types. + * + * @{ + */ + + attr_get_box2i :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + outval: ^attr_box2i_t) -> result_t --- + + attr_set_box2i :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + val: ^attr_box2i_t) -> result_t --- + + attr_get_box2f :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + outval: ^attr_box2f_t) -> result_t --- + + attr_set_box2f :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + val: ^attr_box2f_t) -> result_t --- + + /** @brief Zero-copy query of channel data. + * + * Do not free or manipulate the @p chlist data, or use + * after the lifetime of the context. + */ + attr_get_channels :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + chlist: ^^attr_chlist_t) -> result_t --- + + /** @brief This allows one to quickly copy the channels from one file + * to another. + */ + attr_set_channels :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + channels: ^attr_chlist_t) -> result_t --- + + attr_get_chromaticities :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + chroma: ^attr_chromaticities_t) -> result_t --- + + attr_set_chromaticities :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + chroma: ^attr_chromaticities_t) -> result_t --- + + attr_get_compression :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^compression_t) -> result_t --- + + attr_set_compression :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + comp: compression_t) -> result_t --- + + attr_get_double :: proc(ctxt: const_context_t, part_index: c.int, name: cstring, out: f64) -> result_t --- + + attr_set_double :: proc(ctxt: context_t, part_index: c.int, name: cstring, val: f64) -> result_t --- + + attr_get_envmap :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^envmap_t) -> result_t --- + + attr_set_envmap :: proc(ctxt: context_t, part_index: c.int, name: cstring, emap: envmap_t) -> result_t --- + + attr_get_float :: proc(ctxt: const_context_t, part_index: c.int, name: cstring, out: ^f32) -> result_t --- + + attr_set_float :: proc(ctxt: context_t, part_index: c.int, name: cstring, val: f32) -> result_t --- + + /** @brief Zero-copy query of float data. + * + * Do not free or manipulate the @p out data, or use after the + * lifetime of the context. + */ + attr_get_float_vector :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + sz: ^i32, + out: ^[^]f32) -> result_t --- + + attr_set_float_vector :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + sz: i32, + vals: [^]f32) -> result_t --- + + attr_get_int :: proc(ctxt: const_context_t, part_index: c.int, name: cstring, out: ^i32) -> result_t --- + + attr_set_int :: proc(ctxt: context_t, part_index: c.int, name: cstring, val: i32) -> result_t --- + + attr_get_keycode :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_keycode_t) -> result_t --- + + attr_set_keycode :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + kc: ^attr_keycode_t) -> result_t --- + + attr_get_lineorder :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^lineorder_t) -> result_t --- + + attr_set_lineorder :: proc(ctxt: context_t, part_index: c.int, name: cstring, lo: lineorder_t) -> result_t --- + + attr_get_m33f :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_m33f_t) -> result_t --- + + attr_set_m33f :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + m: ^attr_m33f_t) -> result_t --- + + attr_get_m33d :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_m33d_t) -> result_t --- + + attr_set_m33d :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + m: ^attr_m33d_t) -> result_t --- + + attr_get_m44f :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_m44f_t) -> result_t --- + + attr_set_m44f :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + m: ^attr_m44f_t) -> result_t --- + + attr_get_m44d :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_m44d_t) -> result_t --- + + attr_set_m44d :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + m: ^attr_m44d_t) -> result_t --- + + attr_get_preview :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_preview_t) -> result_t --- + + attr_set_preview :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + p: ^attr_preview_t) -> result_t --- + + attr_get_rational :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_rational_t) -> result_t --- + + attr_set_rational :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + r: ^attr_rational_t) -> result_t --- + + /** @brief Zero-copy query of string value. + * + * Do not modify the string pointed to by @p out, and do not use + * after the lifetime of the context. + */ + attr_get_string :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + length: ^i32, + out: ^cstring) -> result_t --- + + attr_set_string :: proc(ctxt: context_t, part_index: c.int, name: cstring, s: cstring) -> result_t --- + + /** @brief Zero-copy query of string data. + * + * Do not free the strings pointed to by the array. + * + * Must provide @p size. + * + * \p out must be a ``^cstring`` array large enough to hold + * the string pointers for the string vector when provided. + */ + attr_get_string_vector :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + size: ^i32, + out: ^cstring) -> result_t --- + + attr_set_string_vector :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + size: i32, + sv: ^cstring) -> result_t --- + + attr_get_tiledesc :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_tiledesc_t) -> result_t --- + + attr_set_tiledesc :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + td: ^attr_tiledesc_t) -> result_t --- + + attr_get_timecode :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_timecode_t) -> result_t --- + + attr_set_timecode :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + tc: ^attr_timecode_t) -> result_t --- + + attr_get_v2i :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_v2i_t) -> result_t --- + + attr_set_v2i :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + v: ^attr_v2i_t) -> result_t --- + + attr_get_v2f :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_v2f_t) -> result_t --- + + attr_set_v2f :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + v: ^attr_v2f_t) -> result_t --- + + attr_get_v2d :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_v2d_t) -> result_t --- + + attr_set_v2d :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + v: ^attr_v2d_t) -> result_t --- + + attr_get_v3i :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_v3i_t) -> result_t --- + + attr_set_v3i :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + v: ^attr_v3i_t) -> result_t --- + + attr_get_v3f :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_v3f_t) -> result_t --- + + attr_set_v3f :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + v: ^attr_v3f_t) -> result_t --- + + attr_get_v3d :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + out: ^attr_v3d_t) -> result_t --- + + attr_set_v3d :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + v: ^attr_v3d_t) -> result_t --- + + attr_get_user :: proc( + ctxt: const_context_t, + part_index: c.int, + name: cstring, + type: ^cstring, + size: ^i32, + out: ^rawptr) -> result_t --- + + attr_set_user :: proc( + ctxt: context_t, + part_index: c.int, + name: cstring, + type: cstring, + size: i32, + out: rawptr) -> result_t --- + +} \ No newline at end of file diff --git a/vendor/README.md b/vendor/README.md index 14e91ca89..628ea2727 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -119,6 +119,14 @@ See also LICENSE.txt in the `portmidi` directory itself. See also LICENSE in the `ENet` directory itself. +## GGPO + +[GGPO](https://www.ggpo.net/) GGPO Rollback Networking SDK. + +Zero-input latency networking library for peer-to-peer games. + +See also LICENSE in the `GGPO` directory itself. + ## Botan [Botan](https://botan.randombit.net/) Crypto and TLS library. diff --git a/vendor/ggpo/GGPO.lib b/vendor/ggpo/GGPO.lib new file mode 100644 index 000000000..70feb57da Binary files /dev/null and b/vendor/ggpo/GGPO.lib differ diff --git a/vendor/ggpo/LICENSE b/vendor/ggpo/LICENSE new file mode 100644 index 000000000..49e89062c --- /dev/null +++ b/vendor/ggpo/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2019 GroundStorm Studios, LLC. (http://ggpo.net) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/ggpo/ggpo.odin b/vendor/ggpo/ggpo.odin new file mode 100644 index 000000000..c727e4a67 --- /dev/null +++ b/vendor/ggpo/ggpo.odin @@ -0,0 +1,526 @@ +package ggpo + +foreign import lib "GGPO.lib" + +import c "core:c/libc" + +Session :: distinct rawptr + +MAX_PLAYERS :: 4 +MAX_PREDICTION_FRAMES :: 8 +MAX_SPECTATORS :: 32 + +SPECTATOR_INPUT_INTERVAL :: 4 + +PlayerHandle :: distinct c.int +PlayerType :: enum c.int { + LOCAL, + REMOTE, + SPECTATOR, +} + +/* + * The Player structure used to describe players in add_player + * + * size: Should be set to the size_of(Player) + * + * type: One of the PlayerType values describing how inputs should be handled + * Local players must have their inputs updated every frame via + * add_local_inputs. Remote players values will come over the + * network. + * + * player_num: The player number. Should be between 1 and the number of players + * In the game (e.g. in a 2 player game, either 1 or 2). + * + * If type == PLAYERTYPE_REMOTE: + * + * remote.ip_address: The ip address of the ggpo session which will host this + * player. + * + * remote.port: The port where udp packets should be sent to reach this player. + * All the local inputs for this session will be sent to this player at + * ip_address:port. + * + */ + + +Player :: struct { + size: c.int, + type: PlayerType, + player_num: c.int, + using u: struct #raw_union { + local: struct {}, + remove: struct { + ip_address: [32]byte, + port: u16, + }, + }, +} + +LocalEndpoint :: struct { + player_num: c.int, +} + +ErrorCode :: enum c.int { + OK = 0, + SUCCESS = 0, + GENERAL_FAILURE = -1, + INVALID_SESSION = 1, + INVALID_PLAYER_HANDLE = 2, + PLAYER_OUT_OF_RANGE = 3, + PREDICTION_THRESHOLD = 4, + UNSUPPORTED = 5, + NOT_SYNCHRONIZED = 6, + IN_ROLLBACK = 7, + INPUT_DROPPED = 8, + PLAYER_DISCONNECTED = 9, + TOO_MANY_SPECTATORS = 10, + INVALID_REQUEST = 11, +} + +INVALID_HANDLE :: PlayerHandle(-1) + +/* + * The EventCode enumeration describes what type of event just happened. + * + * CONNECTED_TO_PEER - Handshake with the game running on the + * other side of the network has been completed. + * + * SYNCHRONIZING_WITH_PEER - Beginning the synchronization + * process with the client on the other end of the networking. The count + * and total fields in the u.synchronizing struct of the Event + * object indicate progress. + * + * SYNCHRONIZED_WITH_PEER - The synchronziation with this + * peer has finished. + * + * RUNNING - All the clients have synchronized. You may begin + * sending inputs with synchronize_inputs. + * + * DISCONNECTED_FROM_PEER - The network connection on + * the other end of the network has closed. + * + * TIMESYNC - The time synchronziation code has determined + * that this client is too far ahead of the other one and should slow + * down to ensure fairness. The u.timesync.frames_ahead parameter in + * the Event object indicates how many frames the client is. + * + */ +EventCode :: enum c.int { + CONNECTED_TO_PEER = 1000, + SYNCHRONIZING_WITH_PEER = 1001, + SYNCHRONIZED_WITH_PEER = 1002, + RUNNING = 1003, + DISCONNECTED_FROM_PEER = 1004, + TIMESYNC = 1005, + CONNECTION_INTERRUPTED = 1006, + CONNECTION_RESUMED = 1007, +} + +/* + * The Event structure contains an asynchronous event notification sent + * by the on_event callback. See EventCode, above, for a detailed + * explanation of each event. + */ + + Event :: struct { + code: EventCode, + using u: struct #raw_union { + connected: struct { + player: PlayerHandle, + }, + synchronizing: struct { + player: PlayerHandle, + count: c.int, + total: c.int, + }, + synchronized: struct { + player: PlayerHandle, + }, + disconnected: struct { + player: PlayerHandle, + }, + timesync: struct { + frames_ahead: c.int, + }, + connection_interrupted: struct { + player: PlayerHandle, + disconnect_timeout: c.int, + }, + connection_resumed: struct { + player: PlayerHandle, + }, + }, +} + +/* + * The SessionCallbacks structure contains the callback functions that + * your application must implement. GGPO.net will periodically call these + * functions during the game. All callback functions must be implemented. + */ +SessionCallbacks :: struct { + /* + * begin_game callback - This callback has been deprecated. You must + * implement it, but should ignore the 'game' parameter. + */ + begin_game: proc "c" (game: cstring) -> bool, + + /* + * save_game_state - The client should allocate a buffer, copy the + * entire contents of the current game state into it, and copy the + * length into the len parameter. Optionally, the client can compute + * a checksum of the data and store it in the checksum argument. + */ + save_game_state: proc "c" (buffer: ^[^]byte, len: ^c.int, checksum: ^c.int, frame: c.int) -> bool, + + /* + * load_game_state - GGPO.net will call this function at the beginning + * of a rollback. The buffer and len parameters contain a previously + * saved state returned from the save_game_state function. The client + * should make the current game state match the state contained in the + * buffer. + */ + load_game_state: proc "c" (buffer: [^]byte, len: c.int) -> bool, + + /* + * log_game_state - Used in diagnostic testing. The client should use + * the log function to write the contents of the specified save + * state in a human readible form. + */ + log_game_state: proc "c" (filename: cstring, buffer: [^]byte, len: c.int) -> bool, + + /* + * free_buffer - Frees a game state allocated in save_game_state. You + * should deallocate the memory contained in the buffer. + */ + free_buffer: proc "c" (buffer: rawptr), + + /* + * advance_frame - Called during a rollback. You should advance your game + * state by exactly one frame. Before each frame, call synchronize_input + * to retrieve the inputs you should use for that frame. After each frame, + * you should call advance_frame to notify GGPO.net that you're + * finished. + * + * The flags parameter is reserved. It can safely be ignored at this time. + */ + advance_frame: proc "c" (flags: c.int) -> bool, + + /* + * on_event - Notification that something has happened. See the EventCode + * structure above for more information. + */ + on_event: proc "c" (info: ^Event) -> bool, +} + +/* + * The NetworkStats function contains some statistics about the current + * session. + * + * network.send_queue_len - The length of the queue containing UDP packets + * which have not yet been acknowledged by the end client. The length of + * the send queue is a rough indication of the quality of the connection. + * The longer the send queue, the higher the round-trip time between the + * clients. The send queue will also be longer than usual during high + * packet loss situations. + * + * network.recv_queue_len - The number of inputs currently buffered by the + * GGPO.net network layer which have yet to be validated. The length of + * the prediction queue is roughly equal to the current frame number + * minus the frame number of the last packet in the remote queue. + * + * network.ping - The roundtrip packet transmission time as calcuated + * by GGPO.net. This will be roughly equal to the actual round trip + * packet transmission time + 2 the interval at which you call idle + * or advance_frame. + * + * network.kbps_sent - The estimated bandwidth used between the two + * clients, in kilobits per second. + * + * timesync.local_frames_behind - The number of frames GGPO.net calculates + * that the local client is behind the remote client at this instant in + * time. For example, if at this instant the current game client is running + * frame 1002 and the remote game client is running frame 1009, this value + * will mostly likely roughly equal 7. + * + * timesync.remote_frames_behind - The same as local_frames_behind, but + * calculated from the perspective of the remote player. + * + */ +NetworkStats :: struct { + network: struct { + send_queue_len: c.int, + recv_queue_len: c.int, + ping: c.int, + kbps_sent: c.int, + }, + timesync: struct { + local_frames_behind: c.int, + remote_frames_behind: c.int, + }, +} + +@(default_calling_convention="c") +@(link_prefix="ggpo_") +foreign lib { + /* + * start_session -- + * + * Used to being a new GGPO.net session. The ggpo object returned by start_session + * uniquely identifies the state for this session and should be passed to all other + * functions. + * + * session - An out parameter to the new ggpo session object. + * + * cb - A SessionCallbacks structure which contains the callbacks you implement + * to help GGPO.net synchronize the two games. You must implement all functions in + * cb, even if they do nothing but 'return true'; + * + * game - The name of the game. This is used internally for GGPO for logging purposes only. + * + * num_players - The number of players which will be in this game. The number of players + * per session is fixed. If you need to change the number of players or any player + * disconnects, you must start a new session. + * + * input_size - The size of the game inputs which will be passsed to add_local_input. + * + * local_port - The port GGPO should bind to for UDP traffic. + */ + start_session :: proc(session: ^^Session, + cb: ^SessionCallbacks, + game: cstring, + num_players: c.int, + input_size: c.int, + localport: u16) -> ErrorCode --- + + + /* + * add_player -- + * + * Must be called for each player in the session (e.g. in a 3 player session, must + * be called 3 times). + * + * player - A Player struct used to describe the player. + * + * handle - An out parameter to a handle used to identify this player in the future. + * (e.g. in the on_event callbacks). + */ + add_player :: proc(session: ^Session, + player: ^Player, + handle: ^PlayerHandle) -> ErrorCode --- + + + /* + * start_synctest -- + * + * Used to being a new GGPO.net sync test session. During a sync test, every + * frame of execution is run twice: once in prediction mode and once again to + * verify the result of the prediction. If the checksums of your save states + * do not match, the test is aborted. + * + * cb - A SessionCallbacks structure which contains the callbacks you implement + * to help GGPO.net synchronize the two games. You must implement all functions in + * cb, even if they do nothing but 'return true'; + * + * game - The name of the game. This is used internally for GGPO for logging purposes only. + * + * num_players - The number of players which will be in this game. The number of players + * per session is fixed. If you need to change the number of players or any player + * disconnects, you must start a new session. + * + * input_size - The size of the game inputs which will be passsed to add_local_input. + * + * frames - The number of frames to run before verifying the prediction. The + * recommended value is 1. + * + */ + start_synctest :: proc(session: ^^Session, + cb: ^SessionCallbacks, + game: cstring, + num_players: c.int, + input_size: c.int, + frames: c.int) -> ErrorCode --- + + + /* + * start_spectating -- + * + * Start a spectator session. + * + * cb - A SessionCallbacks structure which contains the callbacks you implement + * to help GGPO.net synchronize the two games. You must implement all functions in + * cb, even if they do nothing but 'return true'; + * + * game - The name of the game. This is used internally for GGPO for logging purposes only. + * + * num_players - The number of players which will be in this game. The number of players + * per session is fixed. If you need to change the number of players or any player + * disconnects, you must start a new session. + * + * input_size - The size of the game inputs which will be passsed to add_local_input. + * + * local_port - The port GGPO should bind to for UDP traffic. + * + * host_ip - The IP address of the host who will serve you the inputs for the game. Any + * player partcipating in the session can serve as a host. + * + * host_port - The port of the session on the host + */ + start_spectating :: proc(session: ^^Session, + cb: ^SessionCallbacks, + game: cstring, + num_players: c.int, + input_size: c.int, + local_port: u16, + host_ip: cstring, + host_port: u16) -> ErrorCode --- + + /* + * close_session -- + * Used to close a session. You must call close_session to + * free the resources allocated in start_session. + */ + close_session :: proc(session: ^Session) -> ErrorCode --- + + + /* + * set_frame_delay -- + * + * Change the amount of frames ggpo will delay local input. Must be called + * before the first call to synchronize_input. + */ + set_frame_delay :: proc(session: ^Session, + player: PlayerHandle, + frame_delay: c.int) -> ErrorCode --- + + /* + * idle -- + * Should be called periodically by your application to give GGPO.net + * a chance to do some work. Most packet transmissions and rollbacks occur + * in idle. + * + * timeout - The amount of time GGPO.net is allowed to spend in this function, + * in milliseconds. + */ + idle :: proc(session: ^Session, + timeout: c.int) -> ErrorCode --- + + /* + * add_local_input -- + * + * Used to notify GGPO.net of inputs that should be trasmitted to remote + * players. add_local_input must be called once every frame for + * all player of type PLAYERTYPE_LOCAL. + * + * player - The player handle returned for this player when you called + * add_local_player. + * + * values - The controller inputs for this player. + * + * size - The size of the controller inputs. This must be exactly equal to the + * size passed into start_session. + */ + add_local_input :: proc(session: ^Session, + player: PlayerHandle, + values: rawptr, + size: c.int) -> ErrorCode --- + + /* + * synchronize_input -- + * + * You should call synchronize_input before every frame of execution, + * including those frames which happen during rollback. + * + * values - When the function returns, the values parameter will contain + * inputs for this frame for all players. The values array must be at + * least (size * players) large. + * + * size - The size of the values array. + * + * disconnect_flags - Indicated whether the input in slot (1 << flag) is + * valid. If a player has disconnected, the input in the values array for + * that player will be zeroed and the i-th flag will be set. For example, + * if only player 3 has disconnected, disconnect flags will be 8 (i.e. 1 << 3). + */ + synchronize_input :: proc(session: ^Session, + values: rawptr, + size: c.int, + disconnect_flags: ^c.int) -> ErrorCode --- + + /* + * disconnect_player -- + * + * Disconnects a remote player from a game. Will return ERRORCODE_PLAYER_DISCONNECTED + * if you try to disconnect a player who has already been disconnected. + */ + disconnect_player :: proc(session: ^Session, + player: PlayerHandle) -> ErrorCode --- + + /* + * advance_frame -- + * + * You should call advance_frame to notify GGPO.net that you have + * advanced your gamestate by a single frame. You should call this everytime + * you advance the gamestate by a frame, even during rollbacks. GGPO.net + * may call your save_state callback before this function returns. + */ + advance_frame :: proc(session: ^Session) -> ErrorCode --- + + /* + * get_network_stats -- + * + * Used to fetch some statistics about the quality of the network connection. + * + * player - The player handle returned from the add_player function you used + * to add the remote player. + * + * stats - Out parameter to the network statistics. + */ + get_network_stats :: proc(session: ^Session, + player: PlayerHandle, + stats: ^NetworkStats) -> ErrorCode --- + + /* + * set_disconnect_timeout -- + * + * Sets the disconnect timeout. The session will automatically disconnect + * from a remote peer if it has not received a packet in the timeout window. + * You will be notified of the disconnect via a EVENTCODE_DISCONNECTED_FROM_PEER + * event. + * + * Setting a timeout value of 0 will disable automatic disconnects. + * + * timeout - The time in milliseconds to wait before disconnecting a peer. + */ + set_disconnect_timeout :: proc(session: ^Session, + timeout: c.int) -> ErrorCode --- + + /* + * set_disconnect_notify_start -- + * + * The time to wait before the first EVENTCODE_NETWORK_INTERRUPTED timeout + * will be sent. + * + * timeout - The amount of time which needs to elapse without receiving a packet + * before the EVENTCODE_NETWORK_INTERRUPTED event is sent. + */ + set_disconnect_notify_start :: proc(session: ^Session, + timeout: c.int) -> ErrorCode --- + + /* + * log -- + * + * Used to write to the ggpo.net log. In the current versions of the + * SDK, a log file is only generated if the "quark.log" environment + * variable is set to 1. This will change in future versions of the + * SDK. + */ + log :: proc(session: ^Session, fmt: cstring, #c_vararg args: ..any) --- + /* + * logv -- + * + * A varargs compatible version of log. See log for + * more details. + */ + logv :: proc(session: ^Session, fmt: cstring, args: c.va_list) --- +} \ No newline at end of file diff --git a/vendor/wasm/README.md b/vendor/wasm/README.md index 8567f2eab..4a9345504 100644 --- a/vendor/wasm/README.md +++ b/vendor/wasm/README.md @@ -32,6 +32,7 @@ const runWasm = async (wasm_path, webglCanvasElement, consoleElement) => { const file = await response.arrayBuffer(); const wasm = await WebAssembly.instantiate(file, imports); const exports = wasm.instance.exports; + wasmMemoryInterface.setExports(exports); wasmMemoryInterface.setMemory(exports.memory); exports._start(); diff --git a/vendor/wasm/WebGL/runtime.js b/vendor/wasm/WebGL/runtime.mjs similarity index 97% rename from vendor/wasm/WebGL/runtime.js rename to vendor/wasm/WebGL/runtime.mjs index 3dc5186ca..4d34b9f4c 100644 --- a/vendor/wasm/WebGL/runtime.js +++ b/vendor/wasm/WebGL/runtime.mjs @@ -23,11 +23,11 @@ class WebGLInterface { this.transformFeedbacks = []; this.syncs = []; this.programInfos = {}; - + if (contextSettings === undefined) { contextSettings = {antialias: false}; } - + this.ctx = canvasElement.getContext("webgl2", contextSettings) || canvasElement.getContext("webgl", contextSettings); if (!this.ctx) { return; @@ -38,11 +38,11 @@ class WebGLInterface { this.ctx_version = 1.0; } } - + get mem() { return this.wasmMemoryInterface } - + assertWebGL2() { if (this.ctx_version < 2) { throw new Error("WebGL2 procedure called in a canvas without a WebGL2 context"); @@ -95,19 +95,19 @@ class WebGLInterface { } return source; } - + getWebGL1Interface() { return { DrawingBufferWidth: () => this.ctx.drawingBufferWidth, DrawingBufferHeight: () => this.ctx.drawingBufferHeight, - + IsExtensionSupported: (name_ptr, name_len) => { let name = this.mem.loadString(name_ptr, name_len); let extensions = this.ctx.getSupportedExtensions(); return extensions.indexOf(name) !== -1 }, - - + + GetError: () => { let err = this.lastError; this.recordError(0); @@ -116,7 +116,7 @@ class WebGLInterface { } return this.ctx.getError(); }, - + GetWebGLVersion: (major_ptr, minor_ptr) => { let version = this.ctx.getParameter(0x1F02); if (version.indexOf("WebGL 2.0") !== -1) { @@ -124,7 +124,7 @@ class WebGLInterface { this.mem.storeI32(minor_ptr, 0); return; } - + this.mem.storeI32(major_ptr, 1); this.mem.storeI32(minor_ptr, 0); }, @@ -135,12 +135,12 @@ class WebGLInterface { this.mem.storeI32(minor_ptr, 0); return; } - + this.mem.storeI32(major_ptr, 2); this.mem.storeI32(minor_ptr, 0); }, - - + + ActiveTexture: (x) => { this.ctx.activeTexture(x); }, @@ -180,8 +180,8 @@ class WebGLInterface { BlendFuncSeparate: (srcRGB, dstRGB, srcAlpha, dstAlpha) => { this.ctx.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); }, - - + + BufferData: (target, size, data, usage) => { if (data) { this.ctx.bufferData(target, this.mem.loadBytes(data, size), usage); @@ -196,8 +196,8 @@ class WebGLInterface { this.ctx.bufferSubData(target, offset, null); } }, - - + + Clear: (x) => { this.ctx.clear(x); }, @@ -216,8 +216,8 @@ class WebGLInterface { CompileShader: (shader) => { this.ctx.compileShader(this.shaders[shader]); }, - - + + CompressedTexImage2D: (target, level, internalformat, width, height, border, imageSize, data) => { if (data) { this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, this.mem.loadBytes(data, imageSize)); @@ -232,15 +232,15 @@ class WebGLInterface { this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, null); } }, - + CopyTexImage2D: (target, level, internalformat, x, y, width, height, border) => { this.ctx.copyTexImage2D(target, level, internalformat, x, y, width, height, border); }, CopyTexSubImage2D: (target, level, xoffset, yoffset, x, y, width, height) => { this.ctx.copyTexImage2D(target, level, xoffset, yoffset, x, y, width, height); }, - - + + CreateBuffer: () => { let buffer = this.ctx.createBuffer(); if (!buffer) { @@ -291,13 +291,13 @@ class WebGLInterface { this.textures[id] = texture; return id; }, - - + + CullFace: (mode) => { this.ctx.cullFace(mode); }, - - + + DeleteBuffer: (id) => { let obj = this.buffers[id]; if (obj && id != 0) { @@ -366,8 +366,8 @@ class WebGLInterface { DrawElements: (mode, count, type, indices) => { this.ctx.drawElements(mode, count, type, indices); }, - - + + Enable: (cap) => { this.ctx.enable(cap); }, @@ -389,20 +389,20 @@ class WebGLInterface { FrontFace: (mode) => { this.ctx.frontFace(mode); }, - - + + GenerateMipmap: (target) => { this.ctx.generateMipmap(target); }, - - + + GetAttribLocation: (program, name_ptr, name_len) => { let name = this.mem.loadString(name_ptr, name_len); return this.ctx.getAttribLocation(this.programs[program], name); }, - - - + + + GetProgramParameter: (program, pname) => { return this.ctx.getProgramParameter(this.programs[program], pname) }, @@ -415,7 +415,7 @@ class WebGLInterface { let n = Math.min(buf_len, log.length); log = log.substring(0, n); this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(log)) - + this.mem.storeInt(length_ptr, n); } }, @@ -428,7 +428,7 @@ class WebGLInterface { let n = Math.min(buf_len, log.length); log = log.substring(0, n); this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(log)) - + this.mem.storeInt(length_ptr, n); } }, @@ -452,8 +452,8 @@ class WebGLInterface { this.recordError(1281); } }, - - + + GetUniformLocation: (program, name_ptr, name_len) => { let name = this.mem.loadString(name_ptr, name_len); let arrayOffset = 0; @@ -472,18 +472,18 @@ class WebGLInterface { var uniformInfo = ptable.uniforms[name]; return (uniformInfo && arrayOffset < uniformInfo[0]) ? uniformInfo[1] + arrayOffset : -1 }, - - + + GetVertexAttribOffset: (index, pname) => { return this.ctx.getVertexAttribOffset(index, pname); }, - - + + Hint: (target, mode) => { this.ctx.hint(target, mode); }, - - + + IsBuffer: (buffer) => this.ctx.isBuffer(this.buffers[buffer]), IsEnabled: (enabled) => this.ctx.isEnabled(this.enableds[enabled]), IsFramebuffer: (framebuffer) => this.ctx.isFramebuffer(this.framebuffers[framebuffer]), @@ -491,7 +491,7 @@ class WebGLInterface { IsRenderbuffer: (renderbuffer) => this.ctx.isRenderbuffer(this.renderbuffers[renderbuffer]), IsShader: (shader) => this.ctx.isShader(this.shaders[shader]), IsTexture: (texture) => this.ctx.isTexture(this.textures[texture]), - + LineWidth: (width) => { this.ctx.lineWidth(width); }, @@ -506,8 +506,8 @@ class WebGLInterface { PolygonOffset: (factor, units) => { this.ctx.polygonOffset(factor, units); }, - - + + ReadnPixels: (x, y, width, height, format, type, bufSize, data) => { this.ctx.readPixels(x, y, width, format, type, this.mem.loadBytes(data, bufSize)); }, @@ -524,7 +524,7 @@ class WebGLInterface { let source = this.getSource(shader, strings_ptr, strings_length); this.ctx.shaderSource(this.shaders[shader], source); }, - + StencilFunc: (func, ref, mask) => { this.ctx.stencilFunc(func, ref, mask); }, @@ -543,8 +543,8 @@ class WebGLInterface { StencilOpSeparate: (face, fail, zfail, zpass) => { this.ctx.stencilOpSeparate(face, fail, zfail, zpass); }, - - + + TexImage2D: (target, level, internalformat, width, height, border, format, type, size, data) => { if (data) { this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, this.mem.loadBytes(data, size)); @@ -561,18 +561,18 @@ class WebGLInterface { TexSubImage2D: (target, level, xoffset, yoffset, width, height, format, type, size, data) => { this.ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, this.mem.loadBytes(data, size)); }, - - + + Uniform1f: (location, v0) => { this.ctx.uniform1f(this.uniforms[location], v0); }, Uniform2f: (location, v0, v1) => { this.ctx.uniform2f(this.uniforms[location], v0, v1); }, Uniform3f: (location, v0, v1, v2) => { this.ctx.uniform3f(this.uniforms[location], v0, v1, v2); }, Uniform4f: (location, v0, v1, v2, v3) => { this.ctx.uniform4f(this.uniforms[location], v0, v1, v2, v3); }, - + Uniform1i: (location, v0) => { this.ctx.uniform1i(this.uniforms[location], v0); }, Uniform2i: (location, v0, v1) => { this.ctx.uniform2i(this.uniforms[location], v0, v1); }, Uniform3i: (location, v0, v1, v2) => { this.ctx.uniform3i(this.uniforms[location], v0, v1, v2); }, Uniform4i: (location, v0, v1, v2, v3) => { this.ctx.uniform4i(this.uniforms[location], v0, v1, v2, v3); }, - + UniformMatrix2fv: (location, addr) => { let array = this.mem.loadF32Array(addr, 2*2); this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); @@ -585,15 +585,15 @@ class WebGLInterface { let array = this.mem.loadF32Array(addr, 4*4); this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); }, - + UseProgram: (program) => { - this.ctx.useProgram(this.programs[program]); + if (program) this.ctx.useProgram(this.programs[program]); }, ValidateProgram: (program) => { - this.ctx.validateProgram(this.programs[program]); + if (program) this.ctx.validateProgram(this.programs[program]); }, - - + + VertexAttrib1f: (index, x) => { this.ctx.vertexAttrib1f(index, x); }, @@ -609,13 +609,13 @@ class WebGLInterface { VertexAttribPointer: (index, size, type, normalized, stride, ptr) => { this.ctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); }, - + Viewport: (x, y, w, h) => { this.ctx.viewport(x, y, w, h); }, }; } - + getWebGL2Interface() { return { /* Buffer objects */ @@ -627,7 +627,7 @@ class WebGLInterface { this.assertWebGL2(); this.ctx.getBufferSubData(target, srcByteOffset, this.mem.loadBytes(dst_buffer_ptr, dst_buffer_len), dstOffset, length); }, - + /* Framebuffer objects */ BlitFramebuffer: (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => { this.assertWebGL2(); @@ -642,7 +642,7 @@ class WebGLInterface { let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len); this.ctx.invalidateFramebuffer(target, attachments); }, - InvalidateSubFramebuffer: (target, attachments_ptr, attachments_len, x, y, width, height) => { + InvalidateSubFramebuffer: (target, attachments_ptr, attachments_len, x, y, width, height) => { this.assertWebGL2(); let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len); this.ctx.invalidateSubFramebuffer(target, attachments, x, y, width, height); @@ -651,15 +651,15 @@ class WebGLInterface { this.assertWebGL2(); this.ctx.readBuffer(src); }, - + /* Renderbuffer objects */ RenderbufferStorageMultisample: (target, samples, internalformat, width, height) => { this.assertWebGL2(); this.ctx.renderbufferStorageMultisample(target, samples, internalformat, width, height); }, - + /* Texture objects */ - + TexStorage3D: (target, levels, internalformat, width, height, depth) => { this.assertWebGL2(); this.ctx.texStorage3D(target, level, internalformat, width, heigh, depth); @@ -692,18 +692,18 @@ class WebGLInterface { this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, null); } }, - + CopyTexSubImage3D: (target, level, xoffset, yoffset, zoffset, x, y, width, height) => { this.assertWebGL2(); this.ctx.copyTexImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height); }, - + /* Programs and shaders */ GetFragDataLocation: (program, name_ptr, name_len) => { this.assertWebGL2(); return this.ctx.getFragDataLocation(this.programs[program], this.mem.loadString(name_ptr, name_len)); }, - + /* Uniforms */ Uniform1ui: (location, v0) => { this.assertWebGL2(); @@ -721,7 +721,7 @@ class WebGLInterface { this.assertWebGL2(); this.ctx.uniform4ui(this.uniforms[location], v0, v1, v2, v3); }, - + UniformMatrix3x2fv: (location, addr) => { this.assertWebGL2(); let array = this.mem.loadF32Array(addr, 3*2); @@ -752,21 +752,21 @@ class WebGLInterface { let array = this.mem.loadF32Array(addr, 3*4); this.ctx.uniformMatrix3x4fv(this.uniforms[location], false, array); }, - + /* Vertex attribs */ VertexAttribI4i: (index, x, y, z, w) => { this.assertWebGL2(); this.ctx.vertexAttribI4i(index, x, y, z, w); - }, + }, VertexAttribI4ui: (index, x, y, z, w) => { this.assertWebGL2(); this.ctx.vertexAttribI4ui(index, x, y, z, w); - }, + }, VertexAttribIPointer: (index, size, type, stride, offset) => { this.assertWebGL2(); this.ctx.vertexAttribIPointer(index, size, type, stride, offset); - }, - + }, + /* Writing to the drawing buffer */ VertexAttribDivisor: (index, divisor) => { this.assertWebGL2(); @@ -818,7 +818,7 @@ class WebGLInterface { let id = this.getNewId(this.queries); query.name = id; this.queries[id] = query; - return id; + return id; }, DeleteQuery: (id) => { this.assertWebGL2(); @@ -829,7 +829,7 @@ class WebGLInterface { } }, IsQuery: (query) => { - this.assertWebGL2(); + this.assertWebGL2(); return this.ctx.isQuery(this.queries[query]); }, BeginQuery: (target, query) => { @@ -852,9 +852,9 @@ class WebGLInterface { let id = this.getNewId(this.queries); query.name = id; this.queries[id] = query; - return id; + return id; }, - + /* Sampler Objects */ CreateSampler: () => { this.assertWebGL2(); @@ -862,7 +862,7 @@ class WebGLInterface { let id = this.getNewId(this.samplers); sampler.name = id; this.samplers[id] = sampler; - return id; + return id; }, DeleteSampler: (id) => { this.assertWebGL2(); @@ -873,11 +873,11 @@ class WebGLInterface { } }, IsSampler: (sampler) => { - this.assertWebGL2(); + this.assertWebGL2(); return this.ctx.isSampler(this.samplers[sampler]); }, BindSampler: (unit, sampler) => { - this.assertWebGL2(); + this.assertWebGL2(); this.ctx.bindSampler(unit, this.samplers[Sampler]); }, SamplerParameteri: (sampler, pname, param) => { @@ -888,7 +888,7 @@ class WebGLInterface { this.assertWebGL2(); this.ctx.samplerParameterf(this.samplers[sampler], pname, param); }, - + /* Sync objects */ FenceSync: (condition, flags) => { this.assertWebGL2(); @@ -896,10 +896,10 @@ class WebGLInterface { let id = this.getNewId(this.syncs); sync.name = id; this.syncs[id] = sync; - return id; + return id; }, IsSync: (sync) => { - this.assertWebGL2(); + this.assertWebGL2(); return this.ctx.isSync(this.syncs[sync]); }, DeleteSync: (id) => { @@ -908,7 +908,7 @@ class WebGLInterface { if (obj && id != 0) { this.ctx.deleteSampler(obj); this.syncs[id] = null; - } + } }, ClientWaitSync: (sync, flags, timeout) => { this.assertWebGL2(); @@ -918,8 +918,8 @@ class WebGLInterface { this.assertWebGL2(); this.ctx.waitSync(this.syncs[sync], flags, timeout) ; }, - - + + /* Transform Feedback */ CreateTransformFeedback: () => { this.assertWebGL2(); @@ -927,7 +927,7 @@ class WebGLInterface { let id = this.getNewId(this.transformFeedbacks); transformFeedback.name = id; this.transformFeedbacks[id] = transformFeedback; - return id; + return id; }, DeleteTransformFeedback: (id) => { this.assertWebGL2(); @@ -935,7 +935,7 @@ class WebGLInterface { if (obj && id != 0) { this.ctx.deleteTransformFeedback(obj); this.transformFeedbacks[id] = null; - } + } }, IsTransformFeedback: (tf) => { this.assertWebGL2(); @@ -971,8 +971,8 @@ class WebGLInterface { this.assertWebGL2(); this.ctx.resumeTransformFeedback(); }, - - + + /* Uniform Buffer Objects and Transform Feedback Buffers */ BindBufferBase: (target, index, buffer) => { this.assertWebGL2(); @@ -990,7 +990,7 @@ class WebGLInterface { GetActiveUniformBlockName: (program, uniformBlockIndex, buf_ptr, buf_len, length_ptr) => { this.assertWebGL2(); let name = this.ctx.getActiveUniformBlockName(this.programs[program], uniformBlockIndex); - + let n = Math.min(buf_len, name.length); name = name.substring(0, n); this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(name)) @@ -1000,7 +1000,7 @@ class WebGLInterface { this.assertWebGL2(); this.ctx.uniformBlockBinding(this.programs[program], uniformBlockIndex, uniformBlockBinding); }, - + /* Vertex Array Objects */ CreateVertexArray: () => { this.assertWebGL2(); @@ -1008,7 +1008,7 @@ class WebGLInterface { let id = this.getNewId(this.vaos); vao.name = id; this.vaos[id] = vao; - return id; + return id; }, DeleteVertexArray: (id) => { this.assertWebGL2(); @@ -1019,11 +1019,11 @@ class WebGLInterface { } }, IsVertexArray: (vertexArray) => { - this.assertWebGL2(); + this.assertWebGL2(); return this.ctx.isVertexArray(this.vaos[vertexArray]); }, BindVertexArray: (vertexArray) => { - this.assertWebGL2(); + this.assertWebGL2(); this.ctx.bindVertexArray(this.vaos[vertexArray]); }, }; @@ -1031,4 +1031,4 @@ class WebGLInterface { }; -export {WebGLInterface}; +export {WebGLInterface}; \ No newline at end of file diff --git a/vendor/wasm/js/dom.odin b/vendor/wasm/js/dom.odin new file mode 100644 index 000000000..9f9f2fa96 --- /dev/null +++ b/vendor/wasm/js/dom.odin @@ -0,0 +1,22 @@ +//+build js wasm32 +package wasm_js_interface + +foreign import dom_lib "odin_dom" + +@(default_calling_convention="contextless") +foreign dom_lib { + get_element_value_f64 :: proc(id: string) -> f64 --- + get_element_min_max :: proc(id: string) -> (min, max: f64) --- + set_element_value :: proc(id: string, value: f64) --- +} + +get_element_value_string :: proc(id: string, buf: []byte) -> string { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_element_value_string") + _get_element_value_string :: proc(id: string, buf: []byte) -> int --- + } + n := _get_element_value_string(id, buf) + return string(buf[:n]) + +} diff --git a/vendor/wasm/js/events.odin b/vendor/wasm/js/events.odin new file mode 100644 index 000000000..93ea94ede --- /dev/null +++ b/vendor/wasm/js/events.odin @@ -0,0 +1,318 @@ +//+build js wasm32 +package wasm_js_interface + +foreign import dom_lib "odin_dom" + +Event_Kind :: enum u32 { + Invalid, + + Load, + Unload, + Error, + Resize, + Visibility_Change, + Fullscreen_Change, + Fullscreen_Error, + + Click, + Double_Click, + Mouse_Move, + Mouse_Over, + Mouse_Out, + Mouse_Up, + Mouse_Down, + + Key_Up, + Key_Down, + Key_Press, + + Scroll, + Wheel, + + Focus, + Submit, + Blur, + Change, + Select, + + Animation_Start, + Animation_End, + Animation_Iteration, + Animation_Cancel, + + Copy, + Cut, + Paste, + + // Drag, + // Drag_Start, + // Drag_End, + // Drag_Enter, + // Drag_Leave, + // Drag_Over, + // Drop, + + Pointer_Cancel, + Pointer_Down, + Pointer_Enter, + Pointer_Leave, + Pointer_Move, + Pointer_Over, + Pointer_Up, + Got_Pointer_Capture, + Lost_Pointer_Capture, + Pointer_Lock_Change, + Pointer_Lock_Error, + + Selection_Change, + Selection_Start, + + Touch_Cancel, + Touch_End, + Touch_Move, + Touch_Start, + + Transition_Start, + Transition_End, + Transition_Run, + Transition_Cancel, + +} +event_kind_string := [Event_Kind]string{ + .Invalid = "", + + .Load = "load", + .Unload = "unload", + .Error = "error", + .Resize = "resize", + .Visibility_Change = "visibilitychange", + .Fullscreen_Change = "fullscreenchange", + .Fullscreen_Error = "fullscreenerror", + + .Click = "click", + .Double_Click = "dblclick", + .Mouse_Move = "mousemove", + .Mouse_Over = "mouseover", + .Mouse_Out = "mouseout", + .Mouse_Up = "mouseup", + .Mouse_Down = "mousedown", + + .Key_Up = "keyup", + .Key_Down = "keydown", + .Key_Press = "keypress", + + .Scroll = "scroll", + .Wheel = "wheel", + + .Focus = "focus", + .Submit = "submit", + .Blur = "blur", + .Change = "change", + .Select = "select", + + .Animation_Start = "animationstart", + .Animation_End = "animationend", + .Animation_Iteration = "animationiteration", + .Animation_Cancel = "animationcancel", + + .Copy = "copy", + .Cut = "cut", + .Paste = "paste", + + // .Drag, = "drag", + // .Drag_Start, = "dragstart", + // .Drag_End, = "dragend", + // .Drag_Enter, = "dragenter", + // .Drag_Leave, = "dragleave", + // .Drag_Over, = "dragover", + // .Drop, = "drop", + + .Pointer_Cancel = "pointercancel", + .Pointer_Down = "pointerdown", + .Pointer_Enter = "pointerenter", + .Pointer_Leave = "pointerleave", + .Pointer_Move = "pointermove", + .Pointer_Over = "pointerover", + .Pointer_Up = "pointerup", + .Got_Pointer_Capture = "gotpointercapture", + .Lost_Pointer_Capture = "lostpointercapture", + .Pointer_Lock_Change = "pointerlockchange", + .Pointer_Lock_Error = "pointerlockerror", + + .Selection_Change = "selectionchange", + .Selection_Start = "selectionstart", + + .Transition_Start = "transitionstart", + .Transition_End = "transitionend", + .Transition_Run = "transitionrun", + .Transition_Cancel = "transitioncancel", + + .Touch_Cancel = "touchcancel", + .Touch_End = "touchend", + .Touch_Move = "touchmove", + .Touch_Start = "touchstart", +} + +Delta_Mode :: enum u32 { + Pixel = 0, + Line = 1, + Page = 2, +} + +Key_Location :: enum u8 { + Standard = 0, + Left = 1, + Right = 2, + Numpad = 3, +} + +KEYBOARD_MAX_KEY_SIZE :: 16 +KEYBOARD_MAX_CODE_SIZE :: 16 + +Event_Target_Kind :: enum u32 { + Element = 0, + Document = 1, + Window = 2, +} + +Event_Phase :: enum u8 { + None = 0, + Capturing_Phase = 1, + At_Target = 2, + Bubbling_Phase = 3, +} + +Event :: struct { + kind: Event_Kind, + target_kind: Event_Target_Kind, + current_target_kind: Event_Target_Kind, + id: string, + timestamp: f64, + + phase: Event_Phase, + bubbles: bool, + cancelable: bool, + composed: bool, + is_composing: bool, + is_trusted: bool, + + using data: struct #raw_union #align 8 { + scroll: struct { + delta: [2]f64, + }, + visibility_change: struct { + is_visible: bool, + }, + wheel: struct { + delta: [3]f64, + delta_mode: Delta_Mode, + }, + + key: struct { + key: string, + code: string, + location: Key_Location, + + ctrl: bool, + shift: bool, + alt: bool, + meta: bool, + + repeat: bool, + + _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte, + _code_buf: [KEYBOARD_MAX_KEY_SIZE]byte, + }, + + mouse: struct { + screen: [2]i64, + client: [2]i64, + offset: [2]i64, + page: [2]i64, + movement: [2]i64, + + ctrl: bool, + shift: bool, + alt: bool, + meta: bool, + + button: i16, + buttons: bit_set[0..<16; u16], + }, + }, + + + user_data: rawptr, + callback: proc(e: Event), +} + +@(default_calling_convention="contextless") +foreign dom_lib { + event_stop_propagation :: proc() --- + event_stop_immediate_propagation :: proc() --- + event_prevent_default :: proc() --- +} + +add_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="add_event_listener") + _add_event_listener :: proc(id: string, name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- + } + // TODO: Pointer_Lock_Change etc related stuff for all different browsers + return _add_event_listener(id, event_kind_string[kind], kind, user_data, callback, use_capture) +} + +remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="remove_event_listener") + _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool --- + } + return _remove_event_listener(id, event_kind_string[kind], user_data, callback) +} + +add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="add_window_event_listener") + _add_window_event_listener :: proc(name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- + } + return _add_window_event_listener(event_kind_string[kind], kind, user_data, callback, use_capture) +} + +remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="remove_window_event_listener") + _remove_window_event_listener :: proc(name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool --- + } + return _remove_window_event_listener(event_kind_string[kind], user_data, callback) +} + +remove_event_listener_from_event :: proc(e: Event) -> bool { + if e.id == "" { + return remove_window_event_listener(e.kind, e.user_data, e.callback) + } + return remove_event_listener(e.id, e.kind, e.user_data, e.callback) +} + + + + +@(export, link_name="odin_dom_do_event_callback") +do_event_callback :: proc(user_data: rawptr, callback: proc(e: Event)) { + @(default_calling_convention="contextless") + foreign dom_lib { + init_event_raw :: proc(e: ^Event) --- + } + + if callback != nil { + event := Event{ + user_data = user_data, + callback = callback, + } + init_event_raw(&event) + callback(event) + } +} \ No newline at end of file diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js deleted file mode 100644 index 42ee257eb..000000000 --- a/vendor/wasm/js/runtime.js +++ /dev/null @@ -1,154 +0,0 @@ -class WasmMemoryInterface { - constructor() { - this.memory = null; - } - - setMemory(memory) { - this.memory = memory; - } - - get mem() { - return new DataView(this.memory.buffer); - } - - - loadF32Array(addr, len) { - let array = new Float32Array(this.memory.buffer, addr, len); - return array; - } - loadU32Array(addr, len) { - let array = new Uint32Array(this.memory.buffer, addr, len); - return array; - } - loadI32Array(addr, len) { - let array = new Int32Array(this.memory.buffer, addr, len); - return array; - } - - - loadU8(addr) { return this.mem.getUint8 (addr, true); } - loadI8(addr) { return this.mem.getInt8 (addr, true); } - loadU16(addr) { return this.mem.getUint16 (addr, true); } - loadI16(addr) { return this.mem.getInt16 (addr, true); } - loadU32(addr) { return this.mem.getUint32 (addr, true); } - loadI32(addr) { return this.mem.getInt32 (addr, true); } - loadU64(addr) { - const lo = this.mem.getUint32(addr + 0, true); - const hi = this.mem.getUint32(addr + 4, true); - return lo + hi*4294967296; - }; - loadI64(addr) { - // TODO(bill): loadI64 correctly - const lo = this.mem.getUint32(addr + 0, true); - const hi = this.mem.getUint32(addr + 4, true); - return lo + hi*4294967296; - }; - loadF32(addr) { return this.mem.getFloat32(addr, true); } - loadF64(addr) { return this.mem.getFloat64(addr, true); } - loadInt(addr) { return this.mem.getInt32 (addr, true); } - loadUint(addr) { return this.mem.getUint32 (addr, true); } - - loadPtr(addr) { return this.loadUint(addr); } - - loadBytes(ptr, len) { - return new Uint8Array(this.memory.buffer, ptr, len); - } - - loadString(ptr, len) { - const bytes = this.loadBytes(ptr, len); - return new TextDecoder("utf-8").decode(bytes); - } - - storeU8(addr, value) { this.mem.setUint8 (addr, value, true); } - storeI8(addr, value) { this.mem.setInt8 (addr, value, true); } - storeU16(addr, value) { this.mem.setUint16 (addr, value, true); } - storeI16(addr, value) { this.mem.setInt16 (addr, value, true); } - storeU32(addr, value) { this.mem.setUint32 (addr, value, true); } - storeI32(addr, value) { this.mem.setInt32 (addr, value, true); } - storeU64(addr, value) { - this.mem.setUint32(addr + 0, value, true); - this.mem.setUint32(addr + 4, Math.floor(value / 4294967296), true); - } - storeI64(addr, value) { - // TODO(bill): storeI64 correctly - this.mem.setUint32(addr + 0, value, true); - this.mem.setUint32(addr + 4, Math.floor(value / 4294967296), true); - } - storeF32(addr, value) { this.mem.setFloat32(addr, value, true); } - storeF64(addr, value) { this.mem.setFloat64(addr, value, true); } - storeInt(addr, value) { this.mem.setInt32 (addr, value, true); } - storeUint(addr, value) { this.mem.setUint32 (addr, value, true); } -}; - -function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { - const MAX_INFO_CONSOLE_LINES = 512; - let infoConsoleLines = new Array(); - const addConsoleLine = (line) => { - if (line === undefined) { - return; - } - if (line.endsWith("\n")) { - line = line.substring(0, line.length-1); - } else if (infoConsoleLines.length > 0) { - let prev_line = infoConsoleLines.pop(); - line = prev_line.concat(line); - } - infoConsoleLines.push(line); - - if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { - infoConsoleLines.shift(); - } - - let data = ""; - for (let i = 0; i < infoConsoleLines.length; i++) { - if (i != 0) { - data = data.concat("\n"); - } - data = data.concat(infoConsoleLines[i]); - } - - if (consoleElement !== undefined) { - let info = consoleElement; - info.innerHTML = data; - info.scrollTop = info.scrollHeight; - } - }; - - return { - "env": {}, - "odin_env": { - write: (fd, ptr, len) => { - const str = wasmMemoryInterface.loadString(ptr, len); - if (fd == 1) { - addConsoleLine(str); - return; - } else if (fd == 2) { - addConsoleLine(str); - return; - } else { - throw new Error("Invalid fd to 'write'" + stripNewline(str)); - } - }, - trap: () => { throw new Error() }, - alert: (ptr, len) => { alert(wasmMemoryInterface.loadString(ptr, len)) }, - abort: () => { Module.abort() }, - evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); }, - - time_now: () => { - return performance.now() * 1e6; - }, - - sqrt: (x) => Math.sqrt(x), - sin: (x) => Math.sin(x), - cos: (x) => Math.cos(x), - pow: (x) => Math.pow(x), - fmuladd: (x, y, z) => x*y + z, - ln: (x) => Math.log(x), - exp: (x) => Math.exp(x), - ldexp: (x) => Math.ldexp(x), - }, - }; -} - - -export {WasmMemoryInterface, odinSetupDefaultImports}; \ No newline at end of file diff --git a/vendor/wasm/js/runtime.mjs b/vendor/wasm/js/runtime.mjs new file mode 100644 index 000000000..4306c0f32 --- /dev/null +++ b/vendor/wasm/js/runtime.mjs @@ -0,0 +1,377 @@ +class WasmMemoryInterface { + constructor() { + this.memory = null; + this.exports = null; + } + + setMemory(memory) { + this.memory = memory; + } + + setExports(exports) { + this.exports = exports; + this.listenerMap = {}; + } + + get mem() { + return new DataView(this.memory.buffer); + } + + + loadF32Array(addr, len) { + let array = new Float32Array(this.memory.buffer, addr, len); + return array; + } + loadF64Array(addr, len) { + let array = new Float64Array(this.memory.buffer, addr, len); + return array; + } + loadU32Array(addr, len) { + let array = new Uint32Array(this.memory.buffer, addr, len); + return array; + } + loadI32Array(addr, len) { + let array = new Int32Array(this.memory.buffer, addr, len); + return array; + } + + + loadU8(addr) { return this.mem.getUint8 (addr, true); } + loadI8(addr) { return this.mem.getInt8 (addr, true); } + loadU16(addr) { return this.mem.getUint16 (addr, true); } + loadI16(addr) { return this.mem.getInt16 (addr, true); } + loadU32(addr) { return this.mem.getUint32 (addr, true); } + loadI32(addr) { return this.mem.getInt32 (addr, true); } + loadU64(addr) { + const lo = this.mem.getUint32(addr + 0, true); + const hi = this.mem.getUint32(addr + 4, true); + return lo + hi*4294967296; + }; + loadI64(addr) { + // TODO(bill): loadI64 correctly + const lo = this.mem.getUint32(addr + 0, true); + const hi = this.mem.getUint32(addr + 4, true); + return lo + hi*4294967296; + }; + loadF32(addr) { return this.mem.getFloat32(addr, true); } + loadF64(addr) { return this.mem.getFloat64(addr, true); } + loadInt(addr) { return this.mem.getInt32 (addr, true); } + loadUint(addr) { return this.mem.getUint32 (addr, true); } + + loadPtr(addr) { return this.loadUint(addr); } + + loadBytes(ptr, len) { + return new Uint8Array(this.memory.buffer, ptr, len); + } + + loadString(ptr, len) { + const bytes = this.loadBytes(ptr, len); + return new TextDecoder("utf-8").decode(bytes); + } + + storeU8(addr, value) { this.mem.setUint8 (addr, value, true); } + storeI8(addr, value) { this.mem.setInt8 (addr, value, true); } + storeU16(addr, value) { this.mem.setUint16 (addr, value, true); } + storeI16(addr, value) { this.mem.setInt16 (addr, value, true); } + storeU32(addr, value) { this.mem.setUint32 (addr, value, true); } + storeI32(addr, value) { this.mem.setInt32 (addr, value, true); } + storeU64(addr, value) { + this.mem.setUint32(addr + 0, value, true); + this.mem.setUint32(addr + 4, Math.floor(value / 4294967296), true); + } + storeI64(addr, value) { + // TODO(bill): storeI64 correctly + this.mem.setUint32(addr + 0, value, true); + this.mem.setUint32(addr + 4, Math.floor(value / 4294967296), true); + } + storeF32(addr, value) { this.mem.setFloat32(addr, value, true); } + storeF64(addr, value) { this.mem.setFloat64(addr, value, true); } + storeInt(addr, value) { this.mem.setInt32 (addr, value, true); } + storeUint(addr, value) { this.mem.setUint32 (addr, value, true); } +}; + +function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { + const MAX_INFO_CONSOLE_LINES = 512; + let infoConsoleLines = new Array(); + const addConsoleLine = (line) => { + if (!line) { + return; + } + if (line.endsWith("\n")) { + line = line.substring(0, line.length-1); + } else if (infoConsoleLines.length > 0) { + let prev_line = infoConsoleLines.pop(); + line = prev_line.concat(line); + } + infoConsoleLines.push(line); + + if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { + infoConsoleLines.shift(); + } + + let data = ""; + for (let i = 0; i < infoConsoleLines.length; i++) { + if (i != 0) { + data = data.concat("\n"); + } + data = data.concat(infoConsoleLines[i]); + } + + if (consoleElement) { + let info = consoleElement; + info.innerHTML = data; + info.scrollTop = info.scrollHeight; + } + }; + + let event_temp_data = {}; + + return { + "env": {}, + "odin_env": { + write: (fd, ptr, len) => { + const str = wasmMemoryInterface.loadString(ptr, len); + if (fd == 1) { + addConsoleLine(str); + return; + } else if (fd == 2) { + addConsoleLine(str); + return; + } else { + throw new Error("Invalid fd to 'write'" + stripNewline(str)); + } + }, + trap: () => { throw new Error() }, + alert: (ptr, len) => { alert(wasmMemoryInterface.loadString(ptr, len)) }, + abort: () => { Module.abort() }, + evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); }, + + time_now: () => { + return performance.now() * 1e6; + }, + + sqrt: (x) => Math.sqrt(x), + sin: (x) => Math.sin(x), + cos: (x) => Math.cos(x), + pow: (x) => Math.pow(x), + fmuladd: (x, y, z) => x*y + z, + ln: (x) => Math.log(x), + exp: (x) => Math.exp(x), + ldexp: (x) => Math.ldexp(x), + }, + "odin_dom": { + init_event_raw: (ep) => { + const W = 4; + let offset = ep; + let off = (amount, alignment) => { + if (alignment === undefined) { + alignment = Math.min(amount, W); + } + if (offset % alignment != 0) { + offset += alignment - (offset%alignment); + } + let x = offset; + offset += amount; + return x; + }; + + let wmi = wasmMemoryInterface; + + let e = event_temp_data.event; + + wmi.storeU32(off(4), event_temp_data.name_code); + if (e.target == document) { + wmi.storeU32(off(4), 1); + } else if (e.target == window) { + wmi.storeU32(off(4), 2); + } else { + wmi.storeU32(off(4), 0); + } + if (e.currentTarget == document) { + wmi.storeU32(off(4), 1); + } else if (e.currentTarget == window) { + wmi.storeU32(off(4), 2); + } else { + wmi.storeU32(off(4), 0); + } + + wmi.storeUint(off(W), event_temp_data.id_ptr); + wmi.storeUint(off(W), event_temp_data.id_len); + + wmi.storeF64(off(8), e.timeStamp*1e-3); + + wmi.storeU8(off(1), e.eventPhase); + wmi.storeU8(off(1), !!e.bubbles); + wmi.storeU8(off(1), !!e.cancelable); + wmi.storeU8(off(1), !!e.composed); + wmi.storeU8(off(1), !!e.isComposing); + wmi.storeU8(off(1), !!e.isTrusted); + + let base = off(0, 8); + if (e instanceof MouseEvent) { + wmi.storeI64(off(8), e.screenX); + wmi.storeI64(off(8), e.screenY); + wmi.storeI64(off(8), e.clientX); + wmi.storeI64(off(8), e.clientY); + wmi.storeI64(off(8), e.offsetX); + wmi.storeI64(off(8), e.offsetY); + wmi.storeI64(off(8), e.pageX); + wmi.storeI64(off(8), e.pageY); + wmi.storeI64(off(8), e.movementX); + wmi.storeI64(off(8), e.movementY); + + wmi.storeU8(off(1), !!e.ctrlKey); + wmi.storeU8(off(1), !!e.shiftKey); + wmi.storeU8(off(1), !!e.altKey); + wmi.storeU8(off(1), !!e.metaKey); + + wmi.storeI16(off(2), e.button); + wmi.storeU16(off(2), e.buttons); + } else if (e instanceof KeyboardEvent) { + let keyOffset = off(W*2, W); + let codeOffet = off(W*2, W); + wmi.storeU8(off(1), e.location); + + wmi.storeU8(off(1), !!e.ctrlKey); + wmi.storeU8(off(1), !!e.shiftKey); + wmi.storeU8(off(1), !!e.altKey); + wmi.storeU8(off(1), !!e.metaKey); + + wmi.storeU8(off(1), !!e.repeat); + } else if (e instanceof WheelEvent) { + wmi.storeF64(off(8), e.deltaX); + wmi.storeF64(off(8), e.deltaY); + wmi.storeF64(off(8), e.deltaZ); + wmi.storeU32(off(4), e.deltaMode); + } else if (e instanceof Event) { + if ('scrollX' in e) { + wmi.storeF64(off(8), e.scrollX); + wmi.storeF64(off(8), e.scrollY); + } + } + }, + + add_event_listener: (id_ptr, id_len, name_ptr, name_len, name_code, data, callback, use_capture) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = document.getElementById(id); + if (element == undefined) { + return false; + } + + let listener = (e) => { + const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); + event_temp_data.id_ptr = id_ptr; + event_temp_data.id_len = id_len; + event_temp_data.event = e; + event_temp_data.name_code = name_code; + // console.log(e); + wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); + }; + wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = document.getElementById(id); + if (element == undefined) { + return false; + } + + let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; + if (listener == undefined) { + return false; + } + element.removeEventListener(name, listener); + return true; + }, + + + add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = window; + let listener = (e) => { + const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); + event_temp_data.id_ptr = 0; + event_temp_data.id_len = 0; + event_temp_data.event = e; + event_temp_data.name_code = name_code; + // console.log(e); + wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); + }; + wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + remove_window_event_listener: (name_ptr, name_len, data, callback) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = window; + let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; + if (listener == undefined) { + return false; + } + element.removeEventListener(name, listener); + return true; + }, + + event_stop_propagation: () => { + if (event_temp_data && event_temp_data.event) { + event_temp_data.event.eventStopPropagation(); + } + }, + event_stop_immediate_propagation: () => { + if (event_temp_data && event_temp_data.event) { + event_temp_data.event.eventStopImmediatePropagation(); + } + }, + event_prevent_default: () => { + if (event_temp_data && event_temp_data.event) { + event_temp_data.event.eventPreventDefault(); + } + }, + + get_element_value_f64: (id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + return element ? element.value : 0; + }, + get_element_value_string: (id_ptr, id_len, buf_ptr, buf_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + if (element) { + let str = element.value; + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, str.length); + str = str.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(str)) + return n; + } + } + return 0; + }, + get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + if (element) { + let values = wasmMemoryInterface.loadF64Array(ptr_array2_f64, 2); + values[0] = element.min; + values[1] = element.max; + } + }, + set_element_value: (id_ptr, id_len, value) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + if (element) { + element.value = value; + } + }, + }, + }; +} + + +export {WasmMemoryInterface, odinSetupDefaultImports}; \ No newline at end of file diff --git a/vendor/wasm/loader/loader.mjs b/vendor/wasm/loader/loader.mjs new file mode 100644 index 000000000..1e4acbdf3 --- /dev/null +++ b/vendor/wasm/loader/loader.mjs @@ -0,0 +1,63 @@ +import {WasmMemoryInterface, odinSetupDefaultImports} from "../js/runtime.mjs"; +import {WebGLInterface} from "../WebGL/runtime.mjs"; + +export async function runWasmCanvas(wasmPath, webglCanvasElement, consoleElement, extraForeignImports) { + let wasmMemoryInterface = new WasmMemoryInterface(); + + let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); + let exports = {}; + + if (webglCanvasElement !== undefined) { + let gl_context = new WebGLInterface( + wasmMemoryInterface, + webglCanvasElement, + {antialias: false}, + ); + if (!gl_context.ctx) { + return "WebGL is not available."; + } + imports["webgl"] = gl_context.getWebGL1Interface(); + imports["webgl2"] = gl_context.getWebGL2Interface(); + } + + if (extraForeignImports !== undefined) { + imports = { + ...imports, + ...extraForeignImports, + }; + } + + const response = await fetch(wasmPath); + const file = await response.arrayBuffer(); + const wasm = await WebAssembly.instantiate(file, imports); + exports = wasm.instance.exports; + wasmMemoryInterface.setExports(exports); + wasmMemoryInterface.setMemory(exports.memory); + + exports._start(); + + if (exports.step) { + const odin_ctx = exports.default_context_ptr(); + + let prevTimeStamp = undefined; + const step = (currTimeStamp) => { + if (prevTimeStamp == undefined) { + prevTimeStamp = currTimeStamp; + } + + const dt = (currTimeStamp - prevTimeStamp)*0.001; + prevTimeStamp = currTimeStamp; + exports.step(dt, odin_ctx); + window.requestAnimationFrame(step); + }; + + window.requestAnimationFrame(step); + } + + exports._end(); + + return; +}; + + +export {runWasmCanvas};