├── .editorconfig ├── README.md ├── meson_options.txt ├── test ├── expected_output │ └── testslide_example_tile_3_5_10.png ├── fetch.py ├── match.py ├── compare-fixture.py └── thread_test.c ├── .gitignore ├── src ├── third_party │ ├── ltalloc.h │ └── yxml.h ├── isyntax │ ├── isyntax_reader.h │ ├── isyntax_streamer.h │ ├── isyntax.h │ └── isyntax_dwt.c ├── utils │ ├── timerutils.h │ ├── benaphore.h │ ├── stringutils.h │ ├── memrw.h │ ├── benaphore.c │ ├── block_allocator.h │ ├── timerutils.c │ ├── memrw.c │ ├── stringutils.c │ ├── block_allocator.c │ ├── libtiff_api.h │ ├── mathutils.h │ └── mathutils.c ├── platform │ ├── linux_platform.c │ ├── win32_utils.h │ ├── arena.h │ ├── work_queue.h │ ├── linux_utils.c │ ├── platform.c │ ├── platform.h │ ├── intrinsics.h │ ├── work_queue.c │ ├── win32_utils.c │ └── common.h ├── examples │ ├── isyntax_dirwalk.c │ └── isyntax_example.c └── libisyntax.h ├── LICENSE.txt ├── meson.build └── CMakeLists.txt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Code style 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libisyntax 2 | 3 | A library for decoding whole-slide images in Philips iSyntax format. 4 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'tests', 3 | type : 'boolean', 4 | value : false, 5 | description : 'Build tests' 6 | ) 7 | -------------------------------------------------------------------------------- /test/expected_output/testslide_example_tile_3_5_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amspath/libisyntax/HEAD/test/expected_output/testslide_example_tile_3_5_10.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build*/ 2 | .idea/ 3 | release/ 4 | build/ 5 | *.exe 6 | *.dll 7 | *.app/* 8 | *.desktop 9 | *.ini 10 | *.isyntax 11 | *.json 12 | *.xml 13 | -------------------------------------------------------------------------------- /test/fetch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pathlib 4 | import requests 5 | import shutil 6 | import sys 7 | 8 | url = sys.argv[1] 9 | file = pathlib.Path(sys.argv[2]) 10 | 11 | if not file.exists(): 12 | print(f'Fetching {url}...') 13 | resp = requests.get(url, stream=True) 14 | resp.raise_for_status() 15 | with file.open('wb') as fh: 16 | shutil.copyfileobj(resp.raw, fh) 17 | -------------------------------------------------------------------------------- /src/third_party/ltalloc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include /*a more portable std::size_t definition than stddef.h itself*/ 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | void* ltmalloc(size_t); 8 | void ltfree(void*); 9 | void* ltrealloc(void*, size_t); 10 | void* ltcalloc(size_t, size_t); 11 | void* ltmemalign(size_t, size_t); 12 | void ltsqueeze(size_t); /*return memory to system (see README.md)*/ 13 | size_t ltmsize(void*); 14 | void ltonthreadexit(); 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /test/match.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import re 5 | import subprocess 6 | 7 | parser = argparse.ArgumentParser( 8 | 'match.py', 9 | description='run program with arguments, match stdout against regexes' 10 | ) 11 | parser.add_argument('-e', '--regex', action='append') 12 | parser.add_argument('command') 13 | parser.add_argument('arg', nargs='*') 14 | args = parser.parse_args() 15 | if not args.regex: 16 | raise Exception('No regex specified') 17 | 18 | result = subprocess.run( 19 | [args.command] + args.arg, capture_output=True, text=True, check=True 20 | ) 21 | for regex in args.regex: 22 | if not re.search(regex, result.stdout): 23 | raise Exception(f'Did not match: {regex}: {result.stdout}') 24 | -------------------------------------------------------------------------------- /test/compare-fixture.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import subprocess 5 | import tempfile 6 | 7 | parser = argparse.ArgumentParser( 8 | 'compare_fixture.py', 9 | description='run program with arguments, compare output to fixture' 10 | ) 11 | parser.add_argument( 12 | '-f', '--fixture', type=argparse.FileType('rb'), required=True 13 | ) 14 | parser.add_argument('command') 15 | parser.add_argument('arg', nargs='*') 16 | args = parser.parse_args() 17 | 18 | with tempfile.NamedTemporaryFile() as temp: 19 | subprocess.run( 20 | [args.command] + args.arg + [temp.name], check=True 21 | ) 22 | output = temp.read() 23 | if not output: 24 | raise Exception('Program produced no output') 25 | if output != args.fixture.read(): 26 | raise Exception('Output does not match fixture') 27 | -------------------------------------------------------------------------------- /src/isyntax/isyntax_reader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "isyntax.h" 4 | #include "libisyntax.h" 5 | #include "benaphore.h" 6 | 7 | typedef struct isyntax_tile_list_t { 8 | isyntax_tile_t* head; 9 | isyntax_tile_t* tail; 10 | int count; 11 | const char* dbg_name; 12 | } isyntax_tile_list_t; 13 | 14 | typedef struct isyntax_cache_t { 15 | isyntax_tile_list_t cache_list; 16 | benaphore_t mutex; 17 | // TODO(avirodov): int refcount; 18 | int target_cache_size; 19 | block_allocator_t* ll_coeff_block_allocator; 20 | block_allocator_t* h_coeff_block_allocator; 21 | bool is_block_allocator_owned; 22 | int allocator_block_width; 23 | int allocator_block_height; 24 | } isyntax_cache_t; 25 | 26 | // TODO(avirodov): can this ever fail? 27 | void isyntax_tile_read(isyntax_t* isyntax, isyntax_cache_t* cache, int scale, int tile_x, int tile_y, 28 | uint32_t* pixels_buffer, enum isyntax_pixel_format_t pixel_format); 29 | 30 | void tile_list_init(isyntax_tile_list_t* list, const char* dbg_name); 31 | void tile_list_remove(isyntax_tile_list_t* list, isyntax_tile_t* tile); 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019-2023, Pieter Valkema 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /src/utils/timerutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #include "common.h" 35 | 36 | i64 get_clock(); 37 | float get_seconds_elapsed(i64 start, i64 end); 38 | void platform_sleep(u32 ms); 39 | void platform_sleep_ns(i64 ns); 40 | 41 | #if WINDOWS 42 | void win32_init_timer(); // call this once, before calling get_clock() 43 | #endif 44 | 45 | #ifdef __cplusplus 46 | }; 47 | #endif 48 | 49 | -------------------------------------------------------------------------------- /src/platform/linux_platform.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "common.h" 31 | #include "platform.h" 32 | 33 | #include 34 | #include 35 | 36 | 37 | u8* platform_alloc(size_t size) { 38 | u8* result = (u8*) malloc(size); 39 | if (!result) { 40 | printf("Error: memory allocation failed!\n"); 41 | fatal_error(); 42 | } 43 | return result; 44 | } 45 | 46 | const char* get_default_save_directory() { 47 | struct passwd* pwd = getpwuid(getuid()); 48 | if (pwd) 49 | return pwd->pw_dir; 50 | else 51 | return ""; 52 | }; 53 | -------------------------------------------------------------------------------- /src/utils/benaphore.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | 29 | #pragma once 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | #include "common.h" 36 | 37 | #ifdef _WIN32 38 | #include "windows.h" 39 | #else 40 | #include 41 | #endif 42 | 43 | 44 | typedef struct benaphore_t { 45 | #ifdef _WIN32 46 | HANDLE semaphore; 47 | #else 48 | sem_t* semaphore; 49 | #endif 50 | volatile i32 counter; 51 | } benaphore_t; 52 | 53 | benaphore_t benaphore_create(void); 54 | void benaphore_destroy(benaphore_t* benaphore); 55 | void benaphore_lock(benaphore_t* benaphore); 56 | void benaphore_unlock(benaphore_t* benaphore); 57 | 58 | #ifdef __cplusplus 59 | } 60 | #endif 61 | -------------------------------------------------------------------------------- /src/utils/stringutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #include "common.h" 35 | 36 | void strip_character(char* s, char character_to_strip); 37 | char* find_next_token(const char* s, char separator); 38 | void dots_to_underscores(char* s, i32 max); 39 | const char* one_past_last_slash(const char* s, i32 max); 40 | const char* get_file_extension(const char* filename); 41 | void replace_file_extension(char* filename, i32 max_len, const char* new_ext); 42 | char** split_into_lines(char* buffer, size_t* num_lines); 43 | size_t count_lines(char* buffer); 44 | 45 | #ifdef __cplusplus 46 | }; 47 | #endif 48 | 49 | -------------------------------------------------------------------------------- /src/platform/win32_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #include "common.h" 35 | #include "platform.h" 36 | 37 | #include 38 | 39 | // Windows-specific procedures 40 | wchar_t* win32_string_widen(const char* s, size_t len, wchar_t* buffer); 41 | char* win32_string_narrow(wchar_t* s, char* buffer, size_t buffer_size); 42 | void win32_diagnostic(const char* prefix); 43 | void win32_diagnostic_verbose(const char* prefix); 44 | HANDLE win32_open_overlapped_file_handle(const char* filename); 45 | size_t win32_overlapped_read(thread_memory_t* thread_memory, HANDLE file_handle, void* dest, u32 read_size, i64 offset); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /src/utils/memrw.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #include "common.h" 35 | 36 | typedef struct memrw_t { 37 | u8* data; 38 | i64 cursor; 39 | u64 used_size; 40 | u64 used_count; 41 | u64 capacity; 42 | } memrw_t; 43 | 44 | 45 | void memrw_maybe_grow(memrw_t* buffer, u64 new_size); 46 | u64 memrw_push_back(memrw_t* buffer, void* data, u64 size); 47 | void memrw_init(memrw_t* buffer, u64 capacity); 48 | memrw_t memrw_create(u64 capacity); 49 | void memrw_rewind(memrw_t* buffer); 50 | void memrw_seek(memrw_t* buffer, i64 offset); 51 | i64 memrw_write(const void* src, memrw_t* buffer, i64 bytes_to_write); 52 | i64 memrw_putc(i64 c, memrw_t* buffer); 53 | i64 memrw_write_string(const char* s, memrw_t* buffer); 54 | i64 memrw_string_pool_push(memrw_t* buffer, const char* s); 55 | i64 memrw_printf(memrw_t* buffer, const char* fmt, ...); 56 | #define memrw_write_literal(s, buffer) memrw_write((s), (buffer), COUNT(s)-1) 57 | i64 memrw_read(void* dest, memrw_t* buffer, size_t bytes_to_read); 58 | void memrw_destroy(memrw_t* buffer); 59 | 60 | #ifdef __cplusplus 61 | }; 62 | #endif 63 | -------------------------------------------------------------------------------- /src/utils/benaphore.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "benaphore.h" 29 | 30 | #include "intrinsics.h" 31 | 32 | // Based on: 33 | // https://preshing.com/20120226/roll-your-own-lightweight-mutex/ 34 | 35 | 36 | benaphore_t benaphore_create(void) { 37 | benaphore_t result = {0}; 38 | #ifdef _WIN32 39 | result.semaphore = CreateSemaphore(NULL, 0, 1, NULL); 40 | #else 41 | static i32 counter = 1; 42 | char semaphore_name[64]; 43 | i32 c = atomic_increment(&counter); 44 | snprintf(semaphore_name, sizeof(semaphore_name)-1, "/benaphore%d", c); 45 | result.semaphore = sem_open(semaphore_name, O_CREAT, 0644, 0); 46 | #endif 47 | return result; 48 | } 49 | 50 | void benaphore_destroy(benaphore_t* benaphore) { 51 | #ifdef _WIN32 52 | CloseHandle(benaphore->semaphore); 53 | #else 54 | sem_close(benaphore->semaphore); 55 | #endif 56 | } 57 | 58 | void benaphore_lock(benaphore_t* benaphore) { 59 | if (atomic_increment(&benaphore->counter) > 1) { 60 | #ifdef _WIN32 61 | WaitForSingleObject(benaphore->semaphore, INFINITE); 62 | #else 63 | sem_wait(benaphore->semaphore); 64 | #endif 65 | } 66 | } 67 | 68 | void benaphore_unlock(benaphore_t* benaphore) { 69 | if (atomic_decrement(&benaphore->counter) > 0) { 70 | #ifdef _WIN32 71 | ReleaseSemaphore(benaphore->semaphore, 1, NULL); 72 | #else 73 | sem_post(benaphore->semaphore); 74 | #endif 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/isyntax/isyntax_streamer.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #include "common.h" 35 | #include "mathutils.h" 36 | 37 | typedef struct isyntax_streamer_tile_completed_task_t { 38 | u8* pixel_memory; 39 | i32 scale; 40 | i32 tile_index; 41 | i32 tile_width; 42 | i32 tile_height; 43 | i32 resource_id; 44 | bool want_gpu_residency; 45 | } isyntax_streamer_tile_completed_task_t; 46 | 47 | typedef struct isyntax_streamer_t { 48 | // image_t* image; 49 | // scene_t* scene; 50 | isyntax_t* isyntax; 51 | isyntax_image_t* wsi; 52 | i32 resource_id; // unique identifier associated with a single isyntax_t, use this to be able to discard any callbacks that still might come back after calling isyntax_destroy() 53 | v2f origin_offset; 54 | v2f camera_center; 55 | bounds2f camera_bounds; 56 | bounds2f crop_bounds; 57 | bool is_cropped; 58 | // zoom_state_t zoom; 59 | i32 zoom_level; 60 | work_queue_t* tile_completion_queue; 61 | work_queue_callback_t* tile_completion_callback; 62 | u32 tile_completion_task_identifier; 63 | enum isyntax_pixel_format_t pixel_format; 64 | } isyntax_streamer_t; 65 | 66 | 67 | void isyntax_begin_first_load(isyntax_streamer_t* streamer); 68 | void isyntax_begin_load_tile(isyntax_streamer_t* streamer, i32 scale, i32 tile_x, i32 tile_y); 69 | 70 | 71 | // globals 72 | #if defined(ISYNTAX_STREAMER_IMPL) 73 | #define INIT(...) __VA_ARGS__ 74 | #define extern 75 | #else 76 | #define INIT(...) 77 | #undef extern 78 | #endif 79 | 80 | extern bool is_tile_stream_task_in_progress; 81 | extern bool is_tile_streamer_frame_boundary_passed; 82 | 83 | #undef INIT 84 | #undef extern 85 | 86 | 87 | 88 | #ifdef __cplusplus 89 | } 90 | #endif 91 | -------------------------------------------------------------------------------- /src/utils/block_allocator.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | 29 | #pragma once 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | #include "common.h" 36 | #include "platform.h" // for benaphore 37 | 38 | // See: 39 | // https://github.com/SasLuca/rayfork/blob/rayfork-0.9/source/core/rayfork-core.c 40 | 41 | enum allocator_mode { 42 | ALLOCATOR_MODE_UNKNOWN = 0, 43 | ALLOCATOR_MODE_ALLOC, 44 | ALLOCATOR_MODE_REALLOC, 45 | ALLOCATOR_MODE_FREE, 46 | }; 47 | 48 | typedef struct allocator_t allocator_t; 49 | struct allocator_t { 50 | void* userdata; 51 | void* (*proc)(allocator_t* this_allocator, size_t size_to_allocate, u32 mode, void* ptr_to_free_or_realloc); 52 | }; 53 | 54 | 55 | typedef struct block_allocator_item_t block_allocator_item_t; 56 | struct block_allocator_item_t { 57 | i32 chunk_index; 58 | i32 block_index; 59 | block_allocator_item_t* next; 60 | }; 61 | 62 | typedef struct block_allocator_chunk_t { 63 | size_t used_blocks; 64 | u8* memory; 65 | } block_allocator_chunk_t; 66 | 67 | typedef struct block_allocator_t { 68 | size_t block_size; 69 | i32 chunk_capacity_in_blocks; 70 | size_t chunk_size; 71 | i32 chunk_count; 72 | i32 used_chunks; 73 | block_allocator_chunk_t* chunks; 74 | block_allocator_item_t* free_list_storage; 75 | block_allocator_item_t* free_list; 76 | i32 free_list_length; 77 | benaphore_t lock; 78 | bool is_valid; 79 | } block_allocator_t; 80 | 81 | block_allocator_t block_allocator_create(size_t block_size, size_t max_capacity_in_blocks, size_t chunk_size); 82 | void block_allocator_destroy(block_allocator_t* allocator); 83 | void* block_alloc(block_allocator_t* allocator); 84 | void block_free(block_allocator_t* allocator, void* ptr_to_free); 85 | 86 | #ifdef __cplusplus 87 | }; 88 | #endif 89 | -------------------------------------------------------------------------------- /src/platform/arena.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | typedef struct arena_t { 31 | size_t size; 32 | u8* base; 33 | size_t used; 34 | i32 temp_count; 35 | } arena_t; 36 | 37 | typedef struct temp_memory_t { 38 | arena_t* arena; 39 | size_t used; 40 | i32 temp_index; 41 | } temp_memory_t; 42 | 43 | static inline void init_arena(arena_t* arena, size_t size, void* base) { 44 | arena_t new_arena = { 45 | .size = size, 46 | .base = (u8*) base, 47 | .used = 0, 48 | .temp_count = 0, 49 | }; 50 | *arena = new_arena; 51 | } 52 | 53 | static inline void* arena_current_pos(arena_t* arena) { 54 | return arena->base + arena->used; 55 | } 56 | 57 | #define arena_push_struct(arena, type) ((type*)arena_push_size((arena), sizeof(type))) 58 | #define arena_push_array(arena, count, type) ((type*) arena_push_size((arena), (count)* sizeof(type))) 59 | static inline void* arena_push_size(arena_t* arena, size_t size) { 60 | ASSERT((arena->used + size) <= arena->size); 61 | void* result = arena->base + arena->used; 62 | arena->used += size; 63 | return result; 64 | } 65 | 66 | static inline void arena_align(arena_t* arena, u32 alignment) { 67 | ASSERT(alignment > 0); 68 | i64 pos = (i64)arena->base + arena->used; 69 | i64 new_pos = ((pos + alignment - 1) / alignment) * alignment; 70 | arena->used += (size_t)(new_pos - pos); 71 | } 72 | 73 | static inline temp_memory_t begin_temp_memory(arena_t* arena) { 74 | temp_memory_t result = { 75 | .arena = arena, 76 | .used = arena->used, 77 | .temp_index = arena->temp_count, 78 | }; 79 | ++arena->temp_count; 80 | return result; 81 | } 82 | 83 | static inline void release_temp_memory(temp_memory_t* temp) { 84 | ASSERT(temp->arena->temp_count > 0); 85 | temp->arena->temp_count--; 86 | temp->arena->used = temp->used; 87 | ASSERT(temp->temp_index == temp->arena->temp_count); 88 | } 89 | 90 | static inline i64 arena_get_bytes_left(arena_t* arena) { 91 | return arena->size - arena->used; 92 | } 93 | -------------------------------------------------------------------------------- /src/examples/isyntax_dirwalk.c: -------------------------------------------------------------------------------- 1 | #include "libisyntax.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | #define CHECK_LIBISYNTAX_OK(_libisyntax_call) do { \ 12 | isyntax_error_t result = _libisyntax_call; \ 13 | assert(result == LIBISYNTAX_OK); \ 14 | } while(0) 15 | 16 | 17 | #define LOG_VAR(fmt, var) printf("%s: %s=" fmt "\n", __FUNCTION__, #var, var) 18 | 19 | #ifdef _WIN32 20 | #define PATH_SEP "\\" 21 | #else 22 | #define PATH_SEP "/"; 23 | #endif 24 | 25 | // This program loops through all the .isyntax files in a folder (including subfolders), 26 | // and outputs the barcode for each file to stdout (in comma-separated format). 27 | // This may be useful for large collections of WSIs where you can't tell which is which from the filename. 28 | 29 | // NOTE: the barcode/label information must be preserved for this to work. 30 | // This information might be deleted when using the Philips IMS export function (unless that setting is changed). 31 | 32 | int read_barcode_of_isyntax_files_in_directory(const char* dir, const char* subdir_prefix) { 33 | // Looping through files in a folder. 34 | // Reference: https://stackoverflow.com/questions/1271064/how-do-i-loop-through-all-files-in-a-folder-using-c 35 | struct dirent *dp; 36 | DIR *dfd; 37 | if ((dfd = opendir(dir)) == NULL) { 38 | fprintf(stderr, "Can't open %s\n", dir); 39 | return 0; 40 | } 41 | 42 | char filename[260] ; 43 | while ((dp = readdir(dfd)) != NULL) { 44 | struct stat stbuf ; 45 | sprintf(filename, "%s" PATH_SEP "%s", dir, dp->d_name); 46 | if(stat(filename, &stbuf) == -1) { 47 | printf("Unable to stat file: %s\n" , filename); 48 | continue ; 49 | } 50 | if ((stbuf.st_mode & S_IFMT) == S_IFDIR) { 51 | if (dp->d_name[0] != '.') { 52 | char* subdir_path = malloc(260); 53 | snprintf(subdir_path, 260, "%s" PATH_SEP "%s", dir, dp->d_name); 54 | read_barcode_of_isyntax_files_in_directory(subdir_path, dp->d_name); // recurse into subdirectories 55 | free(subdir_path); 56 | } 57 | continue; // Skip directories 58 | } else { 59 | size_t filename_len = strlen(dp->d_name); 60 | const char* suffix = ".isyntax"; 61 | size_t suffix_len = strlen(suffix); 62 | if (strncmp(dp->d_name + filename_len - suffix_len, suffix, suffix_len) == 0) { 63 | // Open the iSyntax file in a special mode where the header is only read partially (for speed). 64 | isyntax_t* isyntax; 65 | if (libisyntax_open(filename, LIBISYNTAX_OPEN_FLAG_READ_BARCODE_ONLY, &isyntax) == LIBISYNTAX_OK) { 66 | // Print out the path of the .isyntax file + the barcode in .csv format. 67 | if (subdir_prefix) { 68 | printf("%s" PATH_SEP "%s,%s\n", subdir_prefix, dp->d_name, libisyntax_get_barcode(isyntax)); 69 | } else { 70 | printf("%s,%s\n", dp->d_name, libisyntax_get_barcode(isyntax)); 71 | } 72 | libisyntax_close(isyntax); 73 | } 74 | } 75 | } 76 | } 77 | return 0; 78 | } 79 | 80 | int main(int argc, char** argv) { 81 | if (argc <= 1) { 82 | printf("Usage: %s - output filename and barcode (comma-separated) for each iSyntax file in the directory and its subdirectories\n", 83 | argv[0], argv[0], argv[0], argv[0]); 84 | return 0; 85 | } 86 | 87 | libisyntax_init(); 88 | 89 | char* dir = argv[1]; 90 | return read_barcode_of_isyntax_files_in_directory(dir, NULL); 91 | } 92 | -------------------------------------------------------------------------------- /test/thread_test.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | #if WINDOWS 4 | // TODO(avirodov): enable this test after we figured out cross-platform threading. 5 | // More discussion in https://github.com/amspath/libisyntax/issues/16 6 | int main(void) { 7 | printf("Test disabled on windows."); 8 | return 0; 9 | } 10 | #else 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "libisyntax.h" 20 | 21 | typedef struct test_thread_t { 22 | thrd_t thread_id; 23 | atomic_int* atomic_sync_flag; // Spinlock to sync threads. 24 | bool force_sync; 25 | 26 | // TODO(avirodov): implement if needed. Or we can keep making sure that test output comes after test work. 27 | // char* output_buffer; // Avoid console output due to it may be implemented with a mutex or other sync. 28 | // int output_buffer_len; 29 | // int output_buffer_capacity; 30 | 31 | void* arg; 32 | int (*func)(void*); 33 | int result; 34 | } test_thread_t; 35 | 36 | int test_print(void* arg) { 37 | clock_t current_clock = clock(); 38 | printf("test_print tid=%ld currect_clock=%ld\n", thrd_current(), current_clock); 39 | return 0; 40 | } 41 | 42 | int parallel_sync_and_call(void* arg) { 43 | test_thread_t* test_thread = (test_thread_t*)arg; 44 | // Wait in spinlock. 45 | while (test_thread->force_sync && atomic_load(&test_thread->atomic_sync_flag) == 0) { 46 | // noop. 47 | } 48 | 49 | // Run test code. 50 | return test_thread->func(test_thread->arg); 51 | } 52 | 53 | void parallel_run(int (*func)(void*), void* arg, bool force_sync) { 54 | #define n_threads 10 55 | const int n_iterations = 2; 56 | 57 | for (int iter = 0; iter < n_iterations; ++iter) { 58 | printf("== parallel run iter %d ==\n", iter); 59 | atomic_int atomic_sync_flag = 0; 60 | test_thread_t threads[n_threads] = {0}; 61 | 62 | // Spawn the threads. 63 | for (int thread_i = 0; thread_i < n_threads; ++thread_i) { 64 | 65 | threads[thread_i].atomic_sync_flag = &atomic_sync_flag; 66 | threads[thread_i].func = func; 67 | threads[thread_i].arg = arg; 68 | threads[thread_i].force_sync = force_sync; 69 | 70 | int result = thrd_create(&threads[thread_i].thread_id, parallel_sync_and_call, &threads[thread_i]); 71 | assert(result == thrd_success); 72 | } 73 | 74 | // Sync and start the threads. 75 | const int milliseconds_to_nanoseconds = 1000 * 1000; 76 | thrd_sleep(&(struct timespec){.tv_nsec=10 * milliseconds_to_nanoseconds}, NULL); 77 | atomic_store(&atomic_sync_flag, 1); 78 | 79 | // Join the threads. 80 | for (int thread_i = 0; thread_i < n_threads; ++thread_i) { 81 | thrd_join(threads[thread_i].thread_id, &threads[thread_i].result); 82 | } 83 | } 84 | } 85 | 86 | extern atomic_int dbgctr_init_thread_pool_counter; 87 | extern atomic_int dbgctr_init_global_mutexes_created; 88 | 89 | int test_libisyntax_init(void* arg) { 90 | clock_t current_clock = clock(); 91 | isyntax_error_t result = libisyntax_init(); 92 | printf("test_print tid=%ld currect_clock=%ld result=%d init_counter=%d mutexes_created_counter=%d\n", 93 | thrd_current(), current_clock, result, 94 | atomic_load(&dbgctr_init_thread_pool_counter), 95 | atomic_load(&dbgctr_init_global_mutexes_created)); 96 | return (int)result; 97 | } 98 | 99 | int main() { 100 | parallel_run(test_print, NULL, /*force_sync=*/true); 101 | parallel_run(test_libisyntax_init, NULL, /*force_sync=*/true); 102 | return 0; 103 | } 104 | 105 | #endif -------------------------------------------------------------------------------- /src/utils/timerutils.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Timer-related procedures 29 | 30 | #include "common.h" 31 | #include "timerutils.h" 32 | 33 | #if WINDOWS 34 | 35 | #include 36 | 37 | static i64 performance_counter_frequency; 38 | static bool is_sleep_granular; 39 | static bool is_timer_initialized; 40 | 41 | void win32_init_timer() { 42 | LARGE_INTEGER perf_counter_frequency_result; 43 | QueryPerformanceFrequency(&perf_counter_frequency_result); 44 | performance_counter_frequency = perf_counter_frequency_result.QuadPart; 45 | // Make Sleep() more granular 46 | UINT desired_scheduler_granularity_ms = 1; 47 | is_sleep_granular = (timeBeginPeriod(desired_scheduler_granularity_ms) == TIMERR_NOERROR); 48 | is_timer_initialized = true; 49 | } 50 | 51 | i64 get_clock() { 52 | #ifndef NO_TIMER_INITIALIZED_RUNTIME_CHECK 53 | if (!is_timer_initialized) win32_init_timer(); 54 | #else 55 | #ifndef NDEBUG 56 | if (!is_timer_initialized) { 57 | fatal_error("get_clock(): timer not initialized; on Windows, call win32_init_timer() once before calling get_clock()"); 58 | } 59 | #endif 60 | #endif 61 | LARGE_INTEGER result; 62 | QueryPerformanceCounter(&result); 63 | return result.QuadPart; 64 | } 65 | 66 | float get_seconds_elapsed(i64 start, i64 end) { 67 | return (float)(end - start) / (float)performance_counter_frequency; 68 | } 69 | 70 | void platform_sleep(u32 ms) { 71 | Sleep(ms); 72 | } 73 | 74 | void platform_sleep_ns(i64 ns) { 75 | Sleep(ns / 1000000); 76 | } 77 | 78 | #else 79 | 80 | #include 81 | 82 | i64 get_clock() { 83 | struct timespec t = {}; 84 | clock_gettime(CLOCK_MONOTONIC, &t); 85 | return t.tv_nsec + 1e9 * t.tv_sec; 86 | } 87 | 88 | float get_seconds_elapsed(i64 start, i64 end) { 89 | i64 elapsed_nanoseconds = end - start; 90 | float elapsed_seconds = ((float)elapsed_nanoseconds) / 1e9f; 91 | return elapsed_seconds; 92 | } 93 | 94 | void platform_sleep(u32 ms) { 95 | struct timespec tim = {}, tim2 = {}; 96 | tim.tv_sec = 0; 97 | tim.tv_nsec = ms * 1000000; 98 | nanosleep(&tim, &tim2); 99 | } 100 | 101 | void platform_sleep_ns(i64 ns) { 102 | struct timespec tim = {}, tim2 = {}; 103 | tim.tv_sec = 0; 104 | tim.tv_nsec = ns; 105 | nanosleep(&tim, &tim2); 106 | } 107 | #endif 108 | 109 | -------------------------------------------------------------------------------- /src/platform/work_queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | #pragma once 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | #include "common.h" 34 | 35 | #if defined(_WIN32) 36 | #include 37 | #else 38 | #include 39 | #endif 40 | 41 | typedef void (work_queue_callback_t)(int logical_thread_index, void* userdata); 42 | 43 | typedef struct work_queue_entry_t { 44 | bool32 is_valid; 45 | u32 task_identifier; 46 | work_queue_callback_t* callback; 47 | u8 userdata[128]; 48 | } work_queue_entry_t; 49 | 50 | typedef struct work_queue_t { 51 | #if WINDOWS 52 | HANDLE semaphore; 53 | #else 54 | sem_t* semaphore; 55 | #endif 56 | i32 volatile next_entry_to_submit; 57 | i32 volatile next_entry_to_execute; 58 | i32 volatile completion_count; 59 | i32 volatile completion_goal; 60 | i32 volatile start_count; 61 | i32 volatile start_goal; 62 | i32 entry_count; 63 | work_queue_entry_t* entries; 64 | } work_queue_t; 65 | 66 | work_queue_t work_queue_create(const char* semaphore_name, i32 entry_count); 67 | void work_queue_destroy(work_queue_t* queue); 68 | i32 work_queue_get_entry_count(work_queue_t* queue); 69 | bool work_queue_submit_task(work_queue_t* queue, work_queue_callback_t callback, void* userdata, size_t userdata_size); 70 | bool work_queue_submit_notification(work_queue_t* queue, u32 task_identifier, void* userdata, size_t userdata_size); 71 | bool work_queue_submit(work_queue_t* queue, work_queue_callback_t callback, u32 task_identifier, void* userdata, size_t userdata_size); 72 | work_queue_entry_t work_queue_get_next_entry(work_queue_t* queue); 73 | void work_queue_mark_entry_completed(work_queue_t* queue); 74 | bool work_queue_do_work(work_queue_t* queue, int logical_thread_index); 75 | bool work_queue_is_work_in_progress(work_queue_t* queue); 76 | bool work_queue_is_work_waiting_to_start(work_queue_t* queue); 77 | void dummy_work_queue_callback(int logical_thread_index, void* userdata); 78 | void test_multithreading_work_queue(); 79 | 80 | 81 | // globals 82 | #if defined(WORK_QUEUE_IMPL) 83 | #define INIT(...) __VA_ARGS__ 84 | #define extern 85 | #else 86 | #define INIT(...) 87 | #undef extern 88 | #endif 89 | 90 | extern THREAD_LOCAL i32 work_queue_call_depth; 91 | extern work_queue_t global_work_queue; 92 | extern i32 global_worker_thread_idle_count; 93 | 94 | 95 | #undef INIT 96 | #undef extern 97 | 98 | 99 | 100 | #ifdef __cplusplus 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /src/platform/linux_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "common.h" 29 | #include "platform.h" 30 | 31 | int platform_stat(const char* filename, struct stat* st) { 32 | return stat(filename, st); 33 | } 34 | 35 | file_stream_t file_stream_open_for_reading(const char* filename) { 36 | FILE* handle = fopen64(filename, "rb"); 37 | return handle; 38 | } 39 | 40 | file_stream_t file_stream_open_for_writing(const char* filename) { 41 | FILE* handle = fopen64(filename, "wb"); 42 | return handle; 43 | } 44 | 45 | i64 file_stream_read(void* dest, size_t bytes_to_read, file_stream_t file_stream) { 46 | size_t bytes_read = fread(dest, 1, bytes_to_read, file_stream); 47 | return bytes_read; 48 | } 49 | 50 | void file_stream_write(void* source, size_t bytes_to_write, file_stream_t file_stream) { 51 | size_t ret = fwrite(source, 1, bytes_to_write, file_stream); 52 | } 53 | 54 | i64 file_stream_get_filesize(file_stream_t file_stream) { 55 | struct stat st; 56 | if (fstat(fileno(file_stream), &st) == 0) { 57 | i64 filesize = st.st_size; 58 | return filesize; 59 | } else { 60 | return 0; 61 | } 62 | } 63 | 64 | i64 file_stream_get_pos(file_stream_t file_stream) { 65 | fpos_t prev_read_pos = {0}; // NOTE: fpos_t may be a struct! 66 | int ret = fgetpos64(file_stream, &prev_read_pos); // for restoring the file position later 67 | ASSERT(ret == 0); (void)ret; 68 | #ifdef _FPOSOFF 69 | return _FPOSOFF(prev_read_pos); 70 | #else 71 | STATIC_ASSERT(sizeof(off_t) == 8); 72 | // Somehow, it is unclear what is the 'correct' way to convert an fpos_t to a simple integer? 73 | return *(i64*)(&prev_read_pos); 74 | #endif 75 | } 76 | 77 | bool file_stream_set_pos(file_stream_t file_stream, i64 offset) { 78 | fpos_t pos = {offset}; 79 | int ret = fsetpos64(file_stream, &pos); 80 | return (ret == 0); 81 | } 82 | 83 | void file_stream_close(file_stream_t file_stream) { 84 | fclose(file_stream); 85 | } 86 | 87 | file_handle_t open_file_handle_for_simultaneous_access(const char* filename) { 88 | file_handle_t fd = open(filename, O_RDONLY); 89 | if (fd == -1) { 90 | console_print_error("Error: Could not reopen file for asynchronous I/O\n"); 91 | return 0; 92 | } else { 93 | return fd; 94 | } 95 | } 96 | 97 | void file_handle_close(file_handle_t file_handle) { 98 | if (file_handle) { 99 | close(file_handle); 100 | } 101 | } 102 | 103 | size_t file_handle_read_at_offset(void* dest, file_handle_t file_handle, u64 offset, size_t bytes_to_read) { 104 | size_t bytes_read = pread(file_handle, dest, bytes_to_read, offset); 105 | return bytes_read; 106 | } 107 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'libisyntax', 'c', 'cpp', 3 | license : 'BSD-2-Clause', 4 | meson_version : '>=0.51.0', 5 | default_options : [ 6 | 'buildtype=release', 7 | 'c_std=gnu11', 8 | ], 9 | ) 10 | 11 | is_macos = host_machine.system() == 'darwin' 12 | is_windows = host_machine.system() == 'windows' 13 | is_apple_silicon = is_macos and host_machine.cpu() in ['arm', 'aarch64'] 14 | 15 | cc = meson.get_compiler('c') 16 | add_project_arguments( 17 | cc.get_supported_arguments( 18 | # there are many of these, and they're sometimes dependent on compile 19 | # options 20 | '-Wno-unused-function', 21 | '-Wno-unused-but-set-variable', 22 | '-Wno-unused-variable', 23 | ), 24 | language : 'c', 25 | ) 26 | 27 | tiff = dependency('libtiff-4', required : false) 28 | if is_windows 29 | threads = dependency('', required : false) 30 | winmm = cc.find_library('winmm') 31 | else 32 | threads = dependency('threads') 33 | winmm = dependency('', required : false) 34 | endif 35 | 36 | if is_apple_silicon 37 | add_project_arguments( 38 | '-march=armv8.2-a+fp16+simd', 39 | language : 'c' 40 | ) 41 | endif 42 | 43 | isyntax_includes = include_directories( 44 | 'src', 45 | 'src/isyntax', 46 | 'src/platform', 47 | 'src/third_party', 48 | 'src/utils', 49 | ) 50 | 51 | isyntax_source = [ 52 | 'src/isyntax/isyntax.c', 53 | 'src/isyntax/isyntax_reader.c', 54 | 'src/platform/platform.c', 55 | 'src/platform/work_queue.c', 56 | 'src/third_party/ltalloc.cc', 57 | 'src/third_party/yxml.c', 58 | 'src/utils/benaphore.c', 59 | 'src/utils/block_allocator.c', 60 | 'src/utils/timerutils.c', 61 | 'src/libisyntax.c', 62 | ] 63 | 64 | if is_windows 65 | isyntax_source += 'src/platform/win32_utils.c' 66 | else 67 | isyntax_source += 'src/platform/linux_utils.c' 68 | endif 69 | 70 | isyntax = library( 71 | 'isyntax', 72 | isyntax_source, 73 | dependencies : [threads, winmm], 74 | include_directories : isyntax_includes, 75 | install : true, 76 | ) 77 | libisyntax_dep = declare_dependency( 78 | include_directories : include_directories('src'), 79 | link_with : isyntax, 80 | ) 81 | 82 | isyntax_example = executable( 83 | 'isyntax_example', 84 | 'src/examples/isyntax_example.c', 85 | dependencies: [libisyntax_dep], 86 | ) 87 | 88 | if tiff.found() 89 | executable( 90 | 'isyntax-to-tiff', 91 | 'src/examples/isyntax_to_tiff.c', 92 | dependencies : [libisyntax_dep, tiff, winmm, threads], 93 | install : true, 94 | ) 95 | endif 96 | 97 | if get_option('tests') 98 | python = import('python').find_installation( 99 | modules: ['requests'], 100 | ) 101 | 102 | # Thank you https://gitlab.com/BioimageInformaticsGroup/openphi 103 | testslide = custom_target( 104 | 'testslide.isyntax', 105 | command : [ 106 | python, files('test/fetch.py'), 107 | 'https://zenodo.org/record/5037046/files/testslide.isyntax?download=1', 108 | '@OUTPUT@', 109 | ], 110 | console : true, 111 | output : 'testslide.isyntax', 112 | ) 113 | 114 | test('smoke_example_runs_no_args', isyntax_example) 115 | 116 | # Test that we can show levels and that number of tiles shown is as 117 | # expected for this test tile. 118 | test( 119 | 'smoke_example_runs_with_test_slide_showing_levels', 120 | python, 121 | args : [ 122 | files('test/match.py'), 123 | '-e', 'width.*=256', 124 | '-e', 'height.*=384', 125 | isyntax_example, testslide, 126 | ], 127 | ) 128 | 129 | # Regression test that the produced tile pixels did not change from expected. 130 | test( 131 | 'regression_example_tile_3_5_10_pixel_check', 132 | python, 133 | args : [ 134 | files('test/compare-fixture.py'), 135 | '-f', files('test/expected_output/testslide_example_tile_3_5_10.png'), 136 | isyntax_example, testslide, '3', '5', '10', 137 | ], 138 | ) 139 | 140 | if not is_macos 141 | # TODO: fix this test on macOS: fatal error: 'threads.h' file not found 142 | thread_test = executable( 143 | 'thread_test', 144 | 'test/thread_test.c', 145 | dependencies : [libisyntax_dep], 146 | include_directories : [isyntax_includes], 147 | ) 148 | test('smoke_thread_test', thread_test) 149 | endif 150 | endif 151 | -------------------------------------------------------------------------------- /src/examples/isyntax_example.c: -------------------------------------------------------------------------------- 1 | #include "libisyntax.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define STB_IMAGE_WRITE_IMPLEMENTATION 8 | #include "third_party/stb_image_write.h" // for png export 9 | 10 | #define CHECK_LIBISYNTAX_OK(_libisyntax_call) do { \ 11 | isyntax_error_t result = _libisyntax_call; \ 12 | assert(result == LIBISYNTAX_OK); \ 13 | } while(0) 14 | 15 | #define LOG_VAR(fmt, var) printf("%s: %s=" fmt "\n", __FUNCTION__, #var, var) 16 | 17 | void print_isyntax_levels(isyntax_t* isyntax) { 18 | const isyntax_image_t* wsi_image = libisyntax_get_wsi_image(isyntax); 19 | 20 | for (int i = 0; i < libisyntax_image_get_level_count(wsi_image); ++i) { 21 | const isyntax_level_t* level = libisyntax_image_get_level(wsi_image, i); 22 | LOG_VAR("%d", i); 23 | LOG_VAR("%d", libisyntax_level_get_scale(level)); 24 | LOG_VAR("%d", libisyntax_level_get_width_in_tiles(level)); 25 | LOG_VAR("%d", libisyntax_level_get_height_in_tiles(level)); 26 | } 27 | } 28 | 29 | int main(int argc, char** argv) { 30 | 31 | if (argc <= 1) { 32 | printf("Usage: %s - show levels & tiles.\n" 33 | " %s - write a tile to output.png\n" 34 | " %s label - write label image to output.jpg\n" 35 | " %s macro - write macro image to output.jpg\n", 36 | argv[0], argv[0], argv[0], argv[0]); 37 | return 0; 38 | } 39 | 40 | char* filename = argv[1]; 41 | 42 | libisyntax_init(); 43 | 44 | isyntax_t* isyntax; 45 | if (libisyntax_open(filename, /*is_init_allocators=*/0, &isyntax) != LIBISYNTAX_OK) { 46 | printf("Failed to open %s\n", filename); 47 | return -1; 48 | } 49 | printf("Successfully opened %s\n", filename); 50 | 51 | if (argc >= 6) { 52 | int level = atoi(argv[2]); 53 | int tile_x = atoi(argv[3]); 54 | int tile_y = atoi(argv[4]); 55 | const char* output_png = argv[5]; 56 | 57 | LOG_VAR("%d", level); 58 | LOG_VAR("%d", tile_x); 59 | LOG_VAR("%d", tile_y); 60 | LOG_VAR("%s", output_png); 61 | 62 | int32_t tile_width = libisyntax_get_tile_width(isyntax); 63 | int32_t tile_height = libisyntax_get_tile_height(isyntax); 64 | LOG_VAR("%d", tile_width); 65 | LOG_VAR("%d", tile_height); 66 | 67 | isyntax_cache_t *isyntax_cache = NULL; 68 | CHECK_LIBISYNTAX_OK(libisyntax_cache_create("example cache", 2000, &isyntax_cache)); 69 | CHECK_LIBISYNTAX_OK(libisyntax_cache_inject(isyntax_cache, isyntax)); 70 | 71 | // RGBA is what stbi expects. 72 | uint32_t *pixels_rgba = malloc(tile_width * tile_height * 4); 73 | CHECK_LIBISYNTAX_OK(libisyntax_tile_read(isyntax, isyntax_cache, level, tile_x, tile_y, 74 | pixels_rgba, LIBISYNTAX_PIXEL_FORMAT_RGBA)); 75 | 76 | printf("Writing %s...\n", output_png); 77 | stbi_write_png(output_png, tile_width, tile_height, 4, pixels_rgba, tile_width * 4); 78 | printf("Done writing %s.\n", output_png); 79 | 80 | free(pixels_rgba); 81 | libisyntax_cache_destroy(isyntax_cache); 82 | 83 | } else if (argc >= 4) { 84 | 85 | if (strcmp(argv[2], "label") == 0 || strcmp(argv[2], "macro") == 0) { 86 | 87 | const char* output_jpg = argv[3]; 88 | LOG_VAR("%s", output_jpg); 89 | 90 | uint8_t* jpeg_buffer = NULL; 91 | uint32_t jpeg_size = 0; 92 | if (strcmp(argv[2], "label") == 0) { 93 | CHECK_LIBISYNTAX_OK(libisyntax_read_label_image_jpeg(isyntax, &jpeg_buffer, &jpeg_size)); 94 | } else if (strcmp(argv[2], "macro") == 0) { 95 | CHECK_LIBISYNTAX_OK(libisyntax_read_macro_image_jpeg(isyntax, &jpeg_buffer, &jpeg_size)); 96 | } 97 | 98 | if (jpeg_buffer) { 99 | FILE* fp = fopen(output_jpg, "wb"); 100 | if (fp) { 101 | fwrite(jpeg_buffer, jpeg_size, 1, fp); 102 | fclose(fp); 103 | } 104 | free(jpeg_buffer); 105 | } 106 | 107 | } else { 108 | print_isyntax_levels(isyntax); 109 | } 110 | } else { 111 | print_isyntax_levels(isyntax); 112 | } 113 | 114 | libisyntax_close(isyntax); 115 | return 0; 116 | } 117 | -------------------------------------------------------------------------------- /src/utils/memrw.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "common.h" 29 | #include "memrw.h" 30 | 31 | void memrw_maybe_grow(memrw_t* buffer, u64 new_size) { 32 | if (new_size > buffer->capacity) { 33 | u64 new_capacity = next_pow2(new_size); 34 | void* new_ptr = realloc(buffer->data, new_capacity); 35 | if (!new_ptr) fatal_error(); 36 | buffer->data = new_ptr; 37 | //#if DO_DEBUG 38 | // console_print_verbose("memrw_maybe_grow(): expanded buffer size from %u to %u\n", buffer->capacity, new_capacity); 39 | //#endif 40 | buffer->capacity = new_capacity; 41 | } 42 | } 43 | 44 | // TODO: do we actually want this to be a dynamic array, or a read/write stream? 45 | u64 memrw_push_back(memrw_t* buffer, void* data, u64 size) { 46 | u64 new_size = buffer->used_size + size; 47 | memrw_maybe_grow(buffer, new_size); 48 | u64 write_offset = buffer->used_size; 49 | void* write_pos = buffer->data + write_offset; 50 | if (data) { 51 | memcpy(write_pos, data, size); 52 | } else { 53 | memset(write_pos, 0, size); 54 | } 55 | buffer->used_size += size; 56 | buffer->cursor = buffer->used_size; 57 | buffer->used_count += 1; 58 | return write_offset; 59 | } 60 | 61 | void memrw_init(memrw_t* buffer, u64 capacity) { 62 | memset(buffer, 0, sizeof(*buffer)); 63 | buffer->data = (u8*) malloc(capacity); 64 | buffer->capacity = capacity; 65 | } 66 | 67 | memrw_t memrw_create(u64 capacity) { 68 | memrw_t result = {0}; 69 | memrw_init(&result, capacity); 70 | return result; 71 | } 72 | 73 | void memrw_rewind(memrw_t* buffer) { 74 | buffer->used_size = 0; 75 | buffer->used_count = 0; 76 | buffer->cursor = 0; 77 | } 78 | 79 | void memrw_seek(memrw_t* buffer, i64 offset) { 80 | if (offset >= 0 && (i64)offset < buffer->used_size) { 81 | buffer->cursor = offset; 82 | } else { 83 | fatal_error(); 84 | }; 85 | } 86 | 87 | i64 memrw_write(const void* src, memrw_t* buffer, i64 bytes_to_write) { 88 | ASSERT(bytes_to_write >= 0); 89 | memrw_maybe_grow(buffer, buffer->cursor + bytes_to_write); 90 | i64 bytes_left = buffer->capacity - buffer->cursor; 91 | if (bytes_left >= 1) { 92 | bytes_to_write = MIN(bytes_to_write, bytes_left); 93 | memcpy(buffer->data + buffer->cursor, src, bytes_to_write); 94 | buffer->cursor += bytes_to_write; 95 | buffer->used_size = MAX(buffer->cursor, (i64)buffer->used_size); 96 | return bytes_to_write; 97 | } 98 | return 0; 99 | } 100 | 101 | i64 memrw_putc(i64 c, memrw_t* buffer) { 102 | return memrw_write(&c, buffer, 1); 103 | } 104 | 105 | i64 memrw_write_string(const char* s, memrw_t* buffer) { 106 | size_t len = strlen(s); 107 | return memrw_write(s, buffer, len); 108 | } 109 | 110 | // Push a zero-terminated string onto the buffer, and return the offset in the buffer (for use as a string pool) 111 | i64 memrw_string_pool_push(memrw_t* buffer, const char* s) { 112 | i64 cursor = buffer->cursor; 113 | memrw_write_string(s, buffer); 114 | memrw_putc('\0', buffer); 115 | return cursor; 116 | } 117 | 118 | i64 memrw_printf(memrw_t* buffer, const char* fmt, ...) { 119 | char buf[4096]; 120 | va_list args; 121 | va_start(args, fmt); 122 | i32 ret = vsnprintf(buf, sizeof(buf)-1, fmt, args); 123 | buf[sizeof(buf)-1] = 0; 124 | memrw_write_string(buf, buffer); 125 | va_end(args); 126 | return ret; 127 | } 128 | 129 | i64 memrw_read(void* dest, memrw_t* buffer, size_t bytes_to_read) { 130 | i64 bytes_left = buffer->used_size - buffer->cursor; 131 | if (bytes_left >= 1) { 132 | bytes_to_read = MIN(bytes_to_read, (size_t)bytes_left); 133 | memcpy(dest, buffer->data + buffer->cursor, bytes_to_read); 134 | buffer->cursor += bytes_to_read; 135 | return bytes_to_read; 136 | } 137 | return 0; 138 | } 139 | 140 | void memrw_destroy(memrw_t* buffer) { 141 | if (buffer->data) free(buffer->data); 142 | memset(buffer, 0, sizeof(*buffer)); 143 | } 144 | 145 | -------------------------------------------------------------------------------- /src/utils/stringutils.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "common.h" 29 | #include "stringutils.h" 30 | 31 | void strip_character(char* s, char character_to_strip) { 32 | if (!s) return; 33 | char c; 34 | while ((c = *s)) { 35 | if (c == character_to_strip) *s = '\0'; 36 | ++s; 37 | } 38 | } 39 | 40 | char* find_next_token(const char* s, char separator) { 41 | if (!s) return NULL; 42 | char c; 43 | while ((c = *s++)) { 44 | if (c == separator) return (char*)s; 45 | } 46 | return NULL; 47 | } 48 | 49 | void dots_to_underscores(char* s, i32 max) { 50 | for (char* pos = s; pos < s + max; ++pos) { 51 | char c = *pos; 52 | if (c == '\0') break; 53 | if (c == '.') *pos = '_'; 54 | } 55 | } 56 | 57 | const char* one_past_last_slash(const char* s, i32 max) { 58 | i32 len = strnlen(s, max - 1); 59 | i32 stripped_len = 0; 60 | const char* pos = s + len - 1; 61 | for (; pos >= s; --pos) { 62 | char c = *pos; 63 | if (c == '/' || c == '\\') { 64 | break; 65 | } else { 66 | ++stripped_len; 67 | } 68 | } 69 | const char* result = pos + 1; // gone back one too far 70 | ASSERT(stripped_len >= 0 && stripped_len <= len); 71 | return result; 72 | } 73 | 74 | const char* get_file_extension(const char* filename) { 75 | size_t len = strlen(filename); 76 | const char* end = filename + len; 77 | for (const char* pos = end - 1; pos >= filename; --pos) { 78 | if (*pos == '.') { 79 | return pos + 1; 80 | } 81 | if (*pos == '/' || *pos == '\\') { 82 | break; 83 | } 84 | } 85 | return end; // no extension 86 | } 87 | 88 | void replace_file_extension(char* filename, i32 max_len, const char* new_ext) { 89 | size_t new_ext_len = strlen(new_ext); 90 | size_t original_len = strlen(filename); 91 | char* end = filename + original_len; 92 | char* append_pos = end; // where we will add the new extension 93 | // Strip the original extension 94 | for (char* pos = end - 1; pos >= filename; --pos) { 95 | if (*pos == '/' || *pos == '\\') { 96 | // gone too far, default to end of original filename 97 | break; 98 | } 99 | if (*pos == '.') { 100 | if (new_ext_len == 0) { 101 | *pos = '\0'; // done: only strip extension 102 | return; 103 | } else { 104 | append_pos = pos + 1; 105 | break; 106 | } 107 | } 108 | } 109 | // Now append the new extension 110 | const char* new_ext_pos = new_ext; 111 | char* buffer_end = filename + max_len; 112 | for (i32 append_len = (buffer_end - append_pos); append_len > 0; --append_len) { 113 | *append_pos = *new_ext_pos; 114 | if (*new_ext_pos == '\0') break; 115 | ++append_pos; 116 | ++new_ext_pos; 117 | } 118 | } 119 | 120 | char** split_into_lines(char* buffer, size_t* num_lines) { 121 | size_t lines_counted = 0; 122 | size_t capacity = 0; 123 | char** lines = NULL; 124 | bool32 newline = true; 125 | char* pos = buffer; 126 | int c; 127 | do { 128 | c = *pos; 129 | if (c == '\n' || c == '\r') { 130 | *pos = '\0'; 131 | newline = true; 132 | } else if (newline || c == '\0') { 133 | size_t line_index = lines_counted++; 134 | if (lines_counted > capacity) { 135 | capacity = MAX(capacity, 8) * 2; 136 | lines = (char**) realloc(lines, capacity * sizeof(char*)); 137 | } 138 | lines[line_index] = pos; 139 | newline = false; 140 | } 141 | ++pos; 142 | } while (c != '\0'); 143 | if (num_lines) *num_lines = lines_counted; 144 | return lines; 145 | } 146 | 147 | size_t count_lines(char* buffer) { 148 | size_t lines_counted = 0; 149 | bool32 newline = true; 150 | char* pos = buffer; 151 | int c; 152 | do { 153 | c = *pos; 154 | if (c == '\n' || c == '\r') { 155 | newline = true; 156 | } else if (newline || c == '\0') { 157 | lines_counted++; 158 | newline = false; 159 | } 160 | ++pos; 161 | } while (c != '\0'); 162 | return lines_counted; 163 | } 164 | -------------------------------------------------------------------------------- /src/utils/block_allocator.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "block_allocator.h" 29 | 30 | /* 31 | void* block_allocator_proc(allocator_t* this_allocator, size_t size_to_allocate, u32 mode, void* ptr_to_free_or_realloc) { 32 | ASSERT(this_allocator); 33 | void* result = NULL; 34 | switch(mode) { 35 | default: break; 36 | case ALLOCATOR_MODE_ALLOC: { 37 | 38 | } break; 39 | case ALLOCATOR_MODE_REALLOC: { 40 | 41 | } break; 42 | case ALLOCATOR_MODE_FREE: { 43 | 44 | } break; 45 | } 46 | } 47 | */ 48 | 49 | block_allocator_t block_allocator_create(size_t block_size, size_t max_capacity_in_blocks, size_t chunk_size) { 50 | u64 total_capacity = (u64)block_size * (u64)max_capacity_in_blocks; 51 | u64 chunk_count = total_capacity / chunk_size; 52 | u64 chunk_capacity_in_blocks = max_capacity_in_blocks / chunk_count; 53 | block_allocator_t result = {0}; 54 | result.block_size = block_size; 55 | result.chunk_capacity_in_blocks = chunk_capacity_in_blocks; 56 | result.chunk_size = chunk_size; 57 | ASSERT(chunk_count > 0); 58 | result.chunk_count = chunk_count; 59 | result.used_chunks = 1; 60 | result.chunks = calloc(1, chunk_count * sizeof(block_allocator_chunk_t)); 61 | result.chunks[0].memory = (u8*)malloc(chunk_size); 62 | result.free_list_storage = calloc(1, max_capacity_in_blocks * sizeof(block_allocator_item_t)); 63 | result.lock = benaphore_create(); 64 | result.is_valid = true; 65 | return result; 66 | } 67 | 68 | void block_allocator_destroy(block_allocator_t* allocator) { 69 | for (i32 i = 0; i < allocator->used_chunks; ++i) { 70 | block_allocator_chunk_t* chunk = allocator->chunks + i; 71 | if (chunk->memory) free(chunk->memory); 72 | } 73 | if (allocator->chunks) free(allocator->chunks); 74 | if (allocator->free_list_storage) free(allocator->free_list_storage); 75 | benaphore_destroy(&allocator->lock); 76 | free(allocator); 77 | } 78 | 79 | void* block_alloc(block_allocator_t* allocator) { 80 | void* result = NULL; 81 | benaphore_lock(&allocator->lock); 82 | if (allocator->free_list != NULL) { 83 | // Grab a block from the free list 84 | block_allocator_item_t* free_item = allocator->free_list; 85 | result = allocator->chunks[free_item->chunk_index].memory + free_item->block_index * allocator->block_size; 86 | allocator->free_list = free_item->next; 87 | --allocator->free_list_length; 88 | } else { 89 | ASSERT(allocator->used_chunks >= 1); 90 | i32 chunk_index = allocator->used_chunks-1; 91 | block_allocator_chunk_t* current_chunk = allocator->chunks + chunk_index; 92 | if (current_chunk->used_blocks < allocator->chunk_capacity_in_blocks) { 93 | i32 block_index = current_chunk->used_blocks++; 94 | result = current_chunk->memory + block_index * allocator->block_size; 95 | } else { 96 | // Chunk is full, allocate a new chunk 97 | if (allocator->used_chunks < allocator->chunk_count) { 98 | // console_print("block_alloc(): allocating a new chunk\n"); 99 | chunk_index = allocator->used_chunks++; 100 | current_chunk = allocator->chunks + chunk_index; 101 | ASSERT(current_chunk->memory == NULL); 102 | current_chunk->memory = (u8*)malloc(allocator->chunk_size); 103 | i32 block_index = current_chunk->used_blocks++; 104 | result = current_chunk->memory + block_index * allocator->block_size; 105 | } else { 106 | console_print_error("block_alloc(): out of memory!\n"); 107 | fatal_error(); 108 | } 109 | } 110 | } 111 | benaphore_unlock(&allocator->lock); 112 | return result; 113 | } 114 | 115 | void block_free(block_allocator_t* allocator, void* ptr_to_free) { 116 | benaphore_lock(&allocator->lock); 117 | block_allocator_item_t free_item = {0}; 118 | // Find the right chunk 119 | i32 chunk_index = -1; 120 | for (i32 i = 0; i < allocator->used_chunks; ++i) { 121 | block_allocator_chunk_t* chunk = allocator->chunks + i; 122 | bool match = ((u8*)ptr_to_free >= chunk->memory && (u8*)ptr_to_free < (chunk->memory + allocator->chunk_size)); 123 | if (match) { 124 | chunk_index = i; 125 | break; 126 | } 127 | } 128 | if (chunk_index >= 0) { 129 | block_allocator_chunk_t* chunk = allocator->chunks + chunk_index; 130 | free_item.next = allocator->free_list; 131 | free_item.chunk_index = chunk_index; 132 | free_item.block_index = ((u8*)ptr_to_free - chunk->memory) / allocator->block_size; 133 | i32 free_index = allocator->free_list_length++; 134 | allocator->free_list_storage[free_index] = free_item; 135 | allocator->free_list = allocator->free_list_storage + free_index; 136 | benaphore_unlock(&allocator->lock); 137 | } else { 138 | console_print_error("block_free(): invalid pointer!\n"); 139 | fatal_error(); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/platform/platform.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #define FATAL_ERROR_IMPLEMENTATION 29 | #define STB_DS_IMPLEMENTATION 30 | #include "common.h" 31 | 32 | #define PLATFORM_IMPL 33 | #include "platform.h" 34 | #include "intrinsics.h" 35 | 36 | #if APPLE 37 | #include // for sysctlbyname() 38 | #endif 39 | 40 | 41 | 42 | mem_t* platform_allocate_mem_buffer(size_t capacity) { 43 | size_t allocation_size = sizeof(mem_t) + capacity + 1; 44 | mem_t* result = (mem_t*) malloc(allocation_size); 45 | result->len = 0; 46 | result->capacity = capacity; 47 | return result; 48 | } 49 | 50 | mem_t* platform_read_entire_file(const char* filename) { 51 | mem_t* result = NULL; 52 | file_stream_t fp = file_stream_open_for_reading(filename); 53 | if (fp) { 54 | i64 filesize = file_stream_get_filesize(fp); 55 | if (filesize > 0) { 56 | size_t allocation_size = sizeof(mem_t) + filesize + 1; 57 | result = (mem_t*) malloc(allocation_size); 58 | if (result) { 59 | ((u8*)result)[allocation_size-1] = '\0'; 60 | result->len = filesize; 61 | result->capacity = filesize; 62 | size_t bytes_read = file_stream_read(result->data, filesize, fp); 63 | if (bytes_read != filesize) { 64 | fatal_error(); 65 | } 66 | } 67 | } 68 | file_stream_close(fp); 69 | } 70 | return result; 71 | } 72 | 73 | 74 | u64 file_read_at_offset(void* dest, file_stream_t fp, u64 offset, u64 num_bytes) { 75 | i64 prev_read_pos = file_stream_get_pos(fp); 76 | file_stream_set_pos(fp, offset); 77 | u64 result = file_stream_read(dest, num_bytes, fp); 78 | file_stream_set_pos(fp, prev_read_pos); 79 | return result; 80 | } 81 | 82 | bool file_exists(const char* filename) { 83 | return (access(filename, F_OK) != -1); 84 | } 85 | 86 | bool is_directory(const char* path) { 87 | struct stat st = {0}; 88 | platform_stat(path, &st); 89 | return S_ISDIR(st.st_mode); 90 | } 91 | 92 | 93 | void get_system_info(bool verbose) { 94 | system_info_t system_info = {0}; 95 | #if WINDOWS 96 | SYSTEM_INFO win32_system_info; 97 | GetSystemInfo(&win32_system_info); 98 | system_info.logical_cpu_count = (i32)win32_system_info.dwNumberOfProcessors; 99 | system_info.physical_cpu_count = system_info.logical_cpu_count; // TODO(pvalkema): how to read this on Windows? 100 | system_info.os_page_size = win32_system_info.dwPageSize; 101 | #elif APPLE 102 | size_t physical_cpu_count_len = sizeof(system_info.physical_cpu_count); 103 | size_t logical_cpu_count_len = sizeof(system_info.logical_cpu_count); 104 | sysctlbyname("hw.physicalcpu", &system_info.physical_cpu_count, &physical_cpu_count_len, NULL, 0); 105 | sysctlbyname("hw.logicalcpu", &system_info.logical_cpu_count, &logical_cpu_count_len, NULL, 0); 106 | system_info.os_page_size = (u32) getpagesize(); 107 | system_info.page_alignment_mask = ~((u64)(sysconf(_SC_PAGE_SIZE) - 1)); 108 | system_info.is_macos = true; 109 | #elif LINUX 110 | system_info.logical_cpu_count = sysconf( _SC_NPROCESSORS_ONLN ); 111 | system_info.physical_cpu_count = system_info.logical_cpu_count; // TODO(pvalkema): how to read this on Linux? 112 | system_info.os_page_size = (u32) getpagesize(); 113 | system_info.page_alignment_mask = ~((u64)(sysconf(_SC_PAGE_SIZE) - 1)); 114 | #endif 115 | if (verbose) console_print("There are %d logical CPU cores\n", system_info.logical_cpu_count); 116 | system_info.suggested_total_thread_count = MIN(system_info.logical_cpu_count, MAX_THREAD_COUNT); 117 | 118 | //TODO(pvalkema): think about returning this instead of setting global state. 119 | global_system_info = system_info; 120 | } 121 | 122 | 123 | void init_thread_memory(i32 logical_thread_index, system_info_t* system_info) { 124 | // Allocate a private memory buffer 125 | u64 thread_memory_size = MEGABYTES(16); 126 | local_thread_memory = (thread_memory_t*) malloc(thread_memory_size); // how much actually needed? 127 | thread_memory_t* thread_memory = local_thread_memory; 128 | memset(thread_memory, 0, sizeof(thread_memory_t)); 129 | #if !WINDOWS 130 | // TODO(pvalkema): think about whether implement creation of async I/O events is needed here 131 | #endif 132 | thread_memory->thread_memory_raw_size = thread_memory_size; 133 | 134 | u32 os_page_size = system_info->os_page_size; 135 | thread_memory->aligned_rest_of_thread_memory = (void*) 136 | ((((u64)thread_memory + sizeof(thread_memory_t) + os_page_size - 1) / os_page_size) * os_page_size); // round up to next page boundary 137 | thread_memory->thread_memory_usable_size = thread_memory_size - ((u64)thread_memory->aligned_rest_of_thread_memory - (u64)thread_memory); 138 | init_arena(&thread_memory->temp_arena, thread_memory->thread_memory_usable_size, thread_memory->aligned_rest_of_thread_memory); 139 | 140 | } 141 | 142 | 143 | -------------------------------------------------------------------------------- /src/platform/platform.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "common.h" 31 | #include "mathutils.h" 32 | #include "arena.h" 33 | #include "memrw.h" 34 | #include "timerutils.h" 35 | #include "work_queue.h" 36 | #include "benaphore.h" 37 | 38 | 39 | #if WINDOWS 40 | #include 41 | #else 42 | #include 43 | #include 44 | #endif 45 | 46 | #ifdef TARGET_EMSCRIPTEN 47 | #include 48 | #else 49 | #define EMSCRIPTEN_KEEPALIVE 50 | #endif 51 | 52 | 53 | #ifdef __cplusplus 54 | extern "C" { 55 | #endif 56 | 57 | #define MAX_THREAD_COUNT 128 58 | 59 | typedef struct app_state_t app_state_t; 60 | 61 | typedef struct mem_t { 62 | size_t len; 63 | size_t capacity; 64 | u8 data[0]; 65 | } mem_t; 66 | 67 | #if WINDOWS 68 | typedef HANDLE semaphore_handle_t; 69 | typedef HANDLE file_handle_t; 70 | typedef HANDLE file_stream_t; 71 | #else 72 | typedef sem_t* semaphore_handle_t; 73 | typedef int file_handle_t; 74 | typedef FILE* file_stream_t; 75 | #endif 76 | 77 | typedef struct platform_thread_info_t { 78 | i32 logical_thread_index; 79 | work_queue_t* queue; 80 | } platform_thread_info_t; 81 | 82 | #define MAX_ASYNC_IO_EVENTS 32 83 | 84 | typedef struct { 85 | #if WINDOWS 86 | HANDLE async_io_events[MAX_ASYNC_IO_EVENTS]; 87 | i32 async_io_index; 88 | OVERLAPPED overlapped; 89 | #else 90 | // TODO: implement this 91 | #endif 92 | u64 thread_memory_raw_size; 93 | u64 thread_memory_usable_size; // free space from aligned_rest_of_thread_memory onward 94 | void* aligned_rest_of_thread_memory; 95 | u32 pbo; 96 | arena_t temp_arena; 97 | } thread_memory_t; 98 | 99 | typedef struct system_info_t { 100 | u32 os_page_size; 101 | u64 page_alignment_mask; 102 | i32 physical_cpu_count; 103 | i32 logical_cpu_count; 104 | i32 suggested_total_thread_count; 105 | bool is_macos; 106 | } system_info_t; 107 | 108 | 109 | typedef struct directory_listing_t directory_listing_t; 110 | 111 | // Inline procedures as wrappers for system routines 112 | #if WINDOWS 113 | 114 | static inline void platform_semaphore_post(semaphore_handle_t semaphore) { 115 | ReleaseSemaphore(semaphore, 1, NULL); 116 | } 117 | 118 | static inline void platform_semaphore_wait(semaphore_handle_t semaphore) { 119 | WaitForSingleObject(semaphore, INFINITE); 120 | } 121 | 122 | #else // Linux, macOS 123 | 124 | static inline void platform_semaphore_post(semaphore_handle_t semaphore) { 125 | sem_post(semaphore); 126 | } 127 | 128 | static inline void platform_semaphore_wait(semaphore_handle_t semaphore) { 129 | sem_wait(semaphore); 130 | } 131 | 132 | #endif 133 | 134 | u8* platform_alloc(size_t size); 135 | mem_t* platform_allocate_mem_buffer(size_t capacity); 136 | mem_t* platform_read_entire_file(const char* filename); 137 | u64 file_read_at_offset(void* dest, file_stream_t fp, u64 offset, u64 num_bytes); 138 | 139 | int platform_stat(const char* filename, struct stat* st); 140 | file_stream_t file_stream_open_for_reading(const char* filename); 141 | file_stream_t file_stream_open_for_writing(const char* filename); 142 | i64 file_stream_read(void* dest, size_t bytes_to_read, file_stream_t file_stream); 143 | void file_stream_write(void* source, size_t bytes_to_write, file_stream_t file_stream); 144 | i64 file_stream_get_filesize(file_stream_t file_stream); 145 | i64 file_stream_get_pos(file_stream_t file_stream); 146 | bool file_stream_set_pos(file_stream_t file_stream, i64 offset); 147 | void file_stream_close(file_stream_t file_stream); 148 | file_handle_t open_file_handle_for_simultaneous_access(const char* filename); 149 | void file_handle_close(file_handle_t file_handle); 150 | size_t file_handle_read_at_offset(void* dest, file_handle_t file_handle, u64 offset, size_t bytes_to_read); 151 | 152 | 153 | bool file_exists(const char* filename); 154 | bool is_directory(const char* path); 155 | 156 | void get_system_info(bool verbose); 157 | 158 | void init_thread_memory(i32 logical_thread_index, system_info_t* system_info); 159 | 160 | // globals 161 | #if defined(PLATFORM_IMPL) 162 | #define INIT(...) __VA_ARGS__ 163 | #define extern 164 | #else 165 | #define INIT(...) 166 | #undef extern 167 | #endif 168 | 169 | extern THREAD_LOCAL thread_memory_t* local_thread_memory; 170 | static inline temp_memory_t begin_temp_memory_on_local_thread() { return begin_temp_memory(&local_thread_memory->temp_arena); } 171 | 172 | extern int g_argc; 173 | extern const char** g_argv; 174 | extern system_info_t global_system_info; 175 | extern i32 global_worker_thread_count; 176 | extern i32 global_active_worker_thread_count; 177 | extern work_queue_t global_completion_queue; 178 | 179 | extern bool is_verbose_mode INIT(= false); 180 | 181 | 182 | #undef INIT 183 | #undef extern 184 | 185 | #ifdef __cplusplus 186 | } 187 | #endif 188 | 189 | -------------------------------------------------------------------------------- /src/libisyntax.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // API conventions: 6 | // - return type is one of: 7 | // -- appropriate data type for simple getters. For complex getters doing work, isyntax_error_t + out variable. 8 | // -- void if the function doesn't fail at runtime (e.g. destructors). 9 | // -- isyntax_error_t otherwise. 10 | // - Output arguments are last, name prefixed with 'out_'. 11 | // - 'Object operated on' argument is first. 12 | // - Function names are prefixed with 'libisyntax_', as C doesn't have namespaces. Naming convention 13 | // is: libisyntax__(..), where is omitted for the object representing the isyntax file. 14 | // - Functions don't check for null/out of bounds, for efficiency. (they may assert, but that is implementation detail). 15 | // - Booleans are represented as int32_t and prefxied with 'is_' or 'has_'. 16 | // - Enums are represented as int32_t and not the enum type. (enum type size forcing is C23 or C++11). 17 | // - Const applied to pointers is used as a signal that the object will not be modified. 18 | // - Prefer int even for unsigned types, see java rationale. 19 | 20 | 21 | typedef int32_t isyntax_error_t; 22 | 23 | #define LIBISYNTAX_OK 0 24 | // Generic error that the user should not expect to recover from. 25 | #define LIBISYNTAX_FATAL 1 26 | // One of the arguments passed to a function is invalid. 27 | #define LIBISYNTAX_INVALID_ARGUMENT 2 28 | 29 | enum isyntax_pixel_format_t { 30 | _LIBISYNTAX_PIXEL_FORMAT_START = 0x100, 31 | LIBISYNTAX_PIXEL_FORMAT_RGBA, 32 | LIBISYNTAX_PIXEL_FORMAT_BGRA, 33 | _LIBISYNTAX_PIXEL_FORMAT_END, 34 | }; 35 | 36 | enum libisyntax_open_flags_t { 37 | // Set this flag to also initialize the allocators needed for tile loading. 38 | LIBISYNTAX_OPEN_FLAG_INIT_ALLOCATORS = 1, 39 | 40 | // Set this flag to only read the barcode, then abort (if you only need the barcode, this will be faster). 41 | LIBISYNTAX_OPEN_FLAG_READ_BARCODE_ONLY = 2, 42 | }; 43 | 44 | typedef struct isyntax_t isyntax_t; 45 | typedef struct isyntax_image_t isyntax_image_t; 46 | typedef struct isyntax_level_t isyntax_level_t; 47 | typedef struct isyntax_cache_t isyntax_cache_t; 48 | 49 | //== Common API == 50 | // TODO(avirodov): are repeated calls of libisyntax_init() allowed? Currently I believe not. 51 | isyntax_error_t libisyntax_init(); 52 | isyntax_error_t libisyntax_open(const char* filename, enum libisyntax_open_flags_t flags, isyntax_t** out_isyntax); 53 | void libisyntax_close(isyntax_t* isyntax); 54 | 55 | //== Getters API == 56 | int32_t libisyntax_get_tile_width(const isyntax_t* isyntax); 57 | int32_t libisyntax_get_tile_height(const isyntax_t* isyntax); 58 | const isyntax_image_t* libisyntax_get_wsi_image(const isyntax_t* isyntax); 59 | const isyntax_image_t* libisyntax_get_label_image(const isyntax_t* isyntax); 60 | const isyntax_image_t* libisyntax_get_macro_image(const isyntax_t* isyntax); 61 | const char* libisyntax_get_barcode(const isyntax_t* isyntax); 62 | int32_t libisyntax_image_get_level_count(const isyntax_image_t* image); 63 | int32_t libisyntax_image_get_offset_x(const isyntax_image_t* image); 64 | int32_t libisyntax_image_get_offset_y(const isyntax_image_t* image); 65 | const isyntax_level_t* libisyntax_image_get_level(const isyntax_image_t* image, int32_t index); 66 | 67 | int32_t libisyntax_level_get_scale(const isyntax_level_t* level); 68 | int32_t libisyntax_level_get_width_in_tiles(const isyntax_level_t* level); 69 | int32_t libisyntax_level_get_height_in_tiles(const isyntax_level_t* level); 70 | int32_t libisyntax_level_get_width(const isyntax_level_t* level); 71 | int32_t libisyntax_level_get_height(const isyntax_level_t* level); 72 | float libisyntax_level_get_mpp_x(const isyntax_level_t* level); 73 | float libisyntax_level_get_mpp_y(const isyntax_level_t* level); 74 | 75 | //== Cache API == 76 | isyntax_error_t libisyntax_cache_create(const char* debug_name_or_null, int32_t cache_size, 77 | isyntax_cache_t** out_isyntax_cache); 78 | // Note: returns LIBISYNTAX_INVALID_ARGUMENT if isyntax_to_inject was not initialized with is_init_allocators = 0. 79 | // TODO(avirodov): this function will fail if the isyntax object has different block size than the first isyntax injected. 80 | // Block size variation was not observed in practice, and a proper fix may include supporting multiple block sizes 81 | // within isyntax_cache_t implementation. 82 | isyntax_error_t libisyntax_cache_inject(isyntax_cache_t* isyntax_cache, isyntax_t* isyntax); 83 | void libisyntax_cache_destroy(isyntax_cache_t* isyntax_cache); 84 | 85 | 86 | //== Tile API == 87 | // Reads a tile into a user-supplied buffer. Buffer size should be [tile_width * tile_height * 4], as returned by 88 | // `libisyntax_get_tile_width()`/`libisyntax_get_tile_height()`. The caller is responsible for managing the buffer 89 | // allocation/deallocation. 90 | // pixel_format is one of isyntax_pixel_format_t. 91 | isyntax_error_t libisyntax_tile_read(isyntax_t* isyntax, isyntax_cache_t* isyntax_cache, 92 | int32_t level, int64_t tile_x, int64_t tile_y, 93 | uint32_t* pixels_buffer, int32_t pixel_format); 94 | isyntax_error_t libisyntax_read_region(isyntax_t* isyntax, isyntax_cache_t* isyntax_cache, int32_t level, 95 | int64_t x, int64_t y, int64_t width, int64_t height, uint32_t* pixels_buffer, 96 | int32_t pixel_format); 97 | 98 | 99 | isyntax_error_t libisyntax_read_label_image(isyntax_t* isyntax, int32_t* width, int32_t* height, 100 | uint32_t** pixels_buffer, int32_t pixel_format); 101 | isyntax_error_t libisyntax_read_macro_image(isyntax_t* isyntax, int32_t* width, int32_t* height, 102 | uint32_t** pixels_buffer, int32_t pixel_format); 103 | isyntax_error_t libisyntax_read_label_image_jpeg(isyntax_t* isyntax, uint8_t** jpeg_buffer, uint32_t* jpeg_size); 104 | isyntax_error_t libisyntax_read_macro_image_jpeg(isyntax_t* isyntax, uint8_t** jpeg_buffer, uint32_t* jpeg_size); 105 | isyntax_error_t libisyntax_read_icc_profile(isyntax_t* isyntax, isyntax_image_t* image, uint8_t** icc_profile_buffer, uint32_t* icc_profile_size); -------------------------------------------------------------------------------- /src/utils/libtiff_api.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | // Dynamic binding for libtiff. 29 | // Useful for avoiding statically linking the libtiff library, which is a headache on Windows (at least for me). 30 | 31 | #pragma once 32 | #ifndef LIBTIFF_API_H 33 | #define LIBTIFF_API_H 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #if __has_include("tiffio.h") 41 | // We don't actually need this to compile the dynamic binding. 42 | // However, we still want access to the libtiff definitions for the rest of the code. 43 | #include "tiffio.h" 44 | #endif 45 | 46 | #ifdef __cplusplus 47 | extern "C" { 48 | #endif 49 | 50 | typedef struct tiff TIFF; 51 | 52 | #if !defined(WINAPI) 53 | #if defined(_WIN32) 54 | #include 55 | #else 56 | #define WINAPI 57 | #endif 58 | #endif 59 | 60 | 61 | // Prototypes 62 | bool init_libtiff_at_runtime(); 63 | 64 | 65 | 66 | // Globals 67 | #if defined(LIBTIFF_API_IMPL) 68 | #define INIT(...) __VA_ARGS__ 69 | #define extern 70 | #else 71 | #define INIT(...) 72 | #undef extern 73 | #endif 74 | 75 | #define TIFFOpen libtiff_TIFFOpen 76 | #define TIFFSetField libtiff_TIFFSetField 77 | #define TIFFWriteDirectory libtiff_TIFFWriteDirectory 78 | #define TIFFClose libtiff_TIFFClose 79 | #define TIFFWriteTile libtiff_TIFFWriteTile 80 | 81 | extern TIFF * (WINAPI *libtiff_TIFFOpen)(const char *name, const char *mode); 82 | extern int (WINAPI *libtiff_TIFFSetField)(TIFF *tif, uint32_t tag, ...); 83 | extern int (WINAPI *libtiff_TIFFWriteDirectory)(TIFF *tif); 84 | extern void (WINAPI *libtiff_TIFFClose)(TIFF *tif); 85 | extern ssize_t (WINAPI *libtiff_TIFFWriteTile)(TIFF *tif, void *buf, uint32_t x, uint32_t y, uint32_t z, uint16_t s); 86 | 87 | 88 | #undef INIT 89 | #undef extern 90 | 91 | 92 | #if defined(LIBTIFF_API_IMPL) 93 | 94 | // Implementation 95 | 96 | #ifdef _WIN32 97 | #include 98 | #else 99 | #include 100 | #endif 101 | 102 | #ifndef COUNT 103 | #define COUNT(array) (sizeof(array) / sizeof((array)[0])) 104 | #endif 105 | 106 | #ifdef _WIN32 107 | static inline const wchar_t* one_past_last_slash_w(const wchar_t* s, int max) { 108 | if (max <= 0) return s; 109 | size_t len = wcsnlen(s, (size_t)(max - 1)); 110 | size_t stripped_len = 0; 111 | const wchar_t* pos = s + len - 1; 112 | for (; pos >= s; --pos) { 113 | wchar_t c = *pos; 114 | if (c == '/' || c == '\\') { 115 | break; 116 | } else { 117 | ++stripped_len; 118 | } 119 | } 120 | const wchar_t* result = pos + 1; // gone back one too far 121 | assert(stripped_len > 0 && stripped_len <= len); 122 | return result; 123 | } 124 | #endif 125 | 126 | bool init_libtiff_at_runtime() { 127 | #ifdef _WIN32 128 | // Look for DLLs in a libtiff/ folder in the same location as the .exe 129 | wchar_t dll_path[4096]; 130 | GetModuleFileNameW(NULL, dll_path, sizeof(dll_path)); 131 | wchar_t* pos = (wchar_t*)one_past_last_slash_w(dll_path, sizeof(dll_path)); 132 | int chars_left = COUNT(dll_path) - (pos - dll_path); 133 | wcsncpy(pos, L"libtiff", chars_left); 134 | SetDllDirectoryW(dll_path); 135 | 136 | HINSTANCE library_handle = LoadLibraryW(L"libtiff-6.dll"); 137 | if (!library_handle) { 138 | // If DLL not found in the libtiff/ folder, look one folder up (in the same location as the .exe) 139 | *pos = '\0'; 140 | SetDllDirectoryW(dll_path); 141 | library_handle = LoadLibraryW(L"libtiff-6.dll"); 142 | } 143 | SetDllDirectoryW(NULL); 144 | 145 | #elif defined(__APPLE__) 146 | void* library_handle = dlopen("libtiff.dylib", RTLD_LAZY); 147 | if (!library_handle) { 148 | // Check expected library path for MacPorts 149 | library_handle = dlopen("/opt/local/lib/libtiff.dylib", RTLD_LAZY); 150 | if (!library_handle) { 151 | // Check expected library path for Homebrew 152 | library_handle = dlopen("/usr/local/opt/libtiff/lib/libtiff.dylib", RTLD_LAZY); 153 | } 154 | } 155 | #else 156 | void* library_handle = dlopen("libtiff.so", RTLD_LAZY); 157 | if (!library_handle) { 158 | library_handle = dlopen("/usr/local/lib/libtiff.so", RTLD_LAZY); 159 | } 160 | #endif 161 | 162 | bool success = false; 163 | if (library_handle) { 164 | 165 | #ifdef _WIN32 166 | #define GET_PROC(proc) if (!(libtiff_##proc = (void*) GetProcAddress(library_handle, #proc))) goto failed; 167 | #else 168 | #define GET_PROC(proc) if (!(libtiff.proc = (void*) dlsym(library_handle, #proc))) goto failed; 169 | #endif 170 | GET_PROC(TIFFOpen); 171 | GET_PROC(TIFFSetField); 172 | GET_PROC(TIFFWriteDirectory); 173 | GET_PROC(TIFFClose); 174 | GET_PROC(TIFFWriteTile); 175 | #undef GET_PROC 176 | 177 | success = true; 178 | 179 | } else failed: { 180 | #ifdef _WIN32 181 | //win32_diagnostic("LoadLibraryA"); 182 | fprintf(stderr, "LibTIFF not available: could not load libtiff-6.dll\n"); 183 | #elif defined(__APPLE__) 184 | fprintf(stderr, "LibTIFF not available: could not load libtiff.dylib (not installed?)\n"); 185 | #else 186 | fprintf(stderr, "LibTIFF not available: could not load libtiff.so (not installed?)\n"); 187 | #endif 188 | success = false; 189 | } 190 | return success; 191 | } 192 | 193 | #endif // LIBTIFF_API_IMPL 194 | 195 | 196 | #ifdef __cplusplus 197 | } 198 | #endif 199 | 200 | 201 | #endif //LIBTIFF_API_H 202 | 203 | -------------------------------------------------------------------------------- /src/third_party/yxml.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2014 Yoran Heling 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef YXML_H 24 | #define YXML_H 25 | 26 | #include 27 | #include 28 | 29 | #if defined(_MSC_VER) && !defined(__cplusplus) && !defined(inline) 30 | #define inline __inline 31 | #endif 32 | 33 | /* Full API documentation for this library can be found in the "yxml.md" file 34 | * in the yxml git repository, or online at http://dev.yorhel.nl/yxml/man */ 35 | 36 | typedef enum { 37 | YXML_EEOF = -5, /* Unexpected EOF */ 38 | YXML_EREF = -4, /* Invalid character or entity reference (&whatever;) */ 39 | YXML_ECLOSE = -3, /* Close tag does not match open tag ( .. ) */ 40 | YXML_ESTACK = -2, /* Stack overflow (too deeply nested tags or too long element/attribute name) */ 41 | YXML_ESYN = -1, /* Syntax error (unexpected byte) */ 42 | YXML_OK = 0, /* Character consumed, no new token present */ 43 | YXML_ELEMSTART = 1, /* Start of an element: '' or '' */ 46 | YXML_ATTRSTART = 4, /* Attribute: 'Name=..' */ 47 | YXML_ATTRVAL = 5, /* Attribute value */ 48 | YXML_ATTREND = 6, /* End of attribute '.."' */ 49 | YXML_PISTART = 7, /* Start of a processing instruction */ 50 | YXML_PICONTENT = 8, /* Content of a PI */ 51 | YXML_PIEND = 9 /* End of a processing instruction */ 52 | } yxml_ret_t; 53 | 54 | /* When, exactly, are tokens returned? 55 | * 56 | * ' ELEMSTART 58 | * '/' ELEMSTART, '>' ELEMEND 59 | * ' ' ELEMSTART 60 | * '>' 61 | * '/', '>' ELEMEND 62 | * Attr 63 | * '=' ATTRSTART 64 | * "X ATTRVAL 65 | * 'Y' ATTRVAL 66 | * 'Z' ATTRVAL 67 | * '"' ATTREND 68 | * '>' 69 | * '/', '>' ELEMEND 70 | * 71 | * ' ELEMEND 73 | */ 74 | 75 | 76 | typedef struct { 77 | /* PUBLIC (read-only) */ 78 | 79 | /* Name of the current element, zero-length if not in any element. Changed 80 | * after YXML_ELEMSTART. The pointer will remain valid up to and including 81 | * the next non-YXML_ATTR* token, the pointed-to buffer will remain valid 82 | * up to and including the YXML_ELEMEND for the corresponding element. */ 83 | char *elem; 84 | 85 | /* The last read character(s) of an attribute value (YXML_ATTRVAL), element 86 | * data (YXML_CONTENT), or processing instruction (YXML_PICONTENT). Changed 87 | * after one of the respective YXML_ values is returned, and only valid 88 | * until the next yxml_parse() call. Usually, this string only consists of 89 | * a single byte, but multiple bytes are returned in the following cases: 90 | * - "": The two characters "?x" 91 | * - "": The two characters "]x" 92 | * - "": The three characters "]]x" 93 | * - "&#N;" and "&#xN;", where dec(n) > 127. The referenced Unicode 94 | * character is then encoded in multiple UTF-8 bytes. 95 | */ 96 | char data[8]; 97 | 98 | /* Name of the current attribute. Changed after YXML_ATTRSTART, valid up to 99 | * and including the next YXML_ATTREND. */ 100 | char *attr; 101 | 102 | /* Name/target of the current processing instruction, zero-length if not in 103 | * a PI. Changed after YXML_PISTART, valid up to (but excluding) 104 | * the next YXML_PIEND. */ 105 | char *pi; 106 | 107 | /* Line number, byte offset within that line, and total bytes read. These 108 | * values refer to the position _after_ the last byte given to 109 | * yxml_parse(). These are useful for debugging and error reporting. */ 110 | uint64_t byte; 111 | uint64_t total; 112 | uint32_t line; 113 | 114 | 115 | /* PRIVATE */ 116 | int state; 117 | unsigned char *stack; /* Stack of element names + attribute/PI name, separated by \0. Also starts with a \0. */ 118 | size_t stacksize, stacklen; 119 | unsigned reflen; 120 | unsigned quote; 121 | int nextstate; /* Used for '@' state remembering and for the "string" consuming state */ 122 | unsigned ignore; 123 | unsigned char *string; 124 | } yxml_t; 125 | 126 | 127 | #ifdef __cplusplus 128 | extern "C" { 129 | #endif 130 | 131 | void yxml_init(yxml_t *, void *, size_t); 132 | 133 | 134 | yxml_ret_t yxml_parse(yxml_t *, int); 135 | 136 | 137 | /* May be called after the last character has been given to yxml_parse(). 138 | * Returns YXML_OK if the XML document is valid, YXML_EEOF otherwise. Using 139 | * this function isn't really necessary, but can be used to detect documents 140 | * that don't end correctly. In particular, an error is returned when the XML 141 | * document did not contain a (complete) root element, or when the document 142 | * ended while in a comment or processing instruction. */ 143 | yxml_ret_t yxml_eof(yxml_t *); 144 | 145 | #ifdef __cplusplus 146 | } 147 | #endif 148 | 149 | 150 | /* Returns the length of the element name (x->elem), attribute name (x->attr), 151 | * or PI name (x->pi). This function should ONLY be used directly after the 152 | * YXML_ELEMSTART, YXML_ATTRSTART or YXML_PISTART (respectively) tokens have 153 | * been returned by yxml_parse(), calling this at any other time may not give 154 | * the correct results. This function should also NOT be used on strings other 155 | * than x->elem, x->attr or x->pi. */ 156 | static inline size_t yxml_symlen(yxml_t *x, const char *s) { 157 | return (x->stack + x->stacklen) - (const unsigned char*)s; 158 | } 159 | 160 | #endif 161 | 162 | /* vim: set noet sw=4 ts=4: */ 163 | -------------------------------------------------------------------------------- /src/utils/mathutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #define FLOAT_TO_BYTE(x) ((u8)(255.0f * CLAMP((x), 0.0f, 1.0f))) 35 | #define BYTE_TO_FLOAT(x) CLAMP(((float)((x & 0x0000ff))) /255.0f, 0.0f, 1.0f) 36 | #define MAKE_BGRA(r,g,b,a) (((u32)(a)) << 24u | ((u32)(r)) << 16u | ((u32)(g)) << 8u | ((u32)(b)) << 0u) 37 | #define MAKE_RGBA(r,g,b,a) (((u32)(a)) << 24u | ((u32)(r)) << 0u | ((u32)(g)) << 8u | ((u32)(b)) << 16u) 38 | #define BGRA_SET_ALPHA(p, a) (((p) & 0x00FFFFFF) | (((u32)(a)) << 24u)) 39 | 40 | #pragma pack(push, 1) 41 | typedef struct rect2i { 42 | i32 x, y, w, h; 43 | } rect2i; 44 | FORCE_INLINE rect2i RECT2I(i32 x, i32 y, i32 w, i32 h) {rect2i r = {x, y, w, h}; return r;} 45 | 46 | 47 | typedef struct rect2f { 48 | float x, y, w, h; 49 | } rect2f; 50 | FORCE_INLINE rect2f RECT2F(float x, float y, float w, float h) {rect2f r = {x, y, w, h}; return r;} 51 | 52 | typedef struct v2i { 53 | i32 x, y; 54 | } v2i; 55 | FORCE_INLINE v2i V2I(i32 x, i32 y) {v2i v = {x, y}; return v; } 56 | 57 | typedef struct rgba_t { 58 | union { 59 | struct { u8 r, g, b, a; }; 60 | u8 values[4]; 61 | }; 62 | } rgba_t; 63 | FORCE_INLINE rgba_t RGBA(u8 r, u8 g, u8 b, u8 a) {rgba_t rgba = {{{r, g, b, a}}}; return rgba; } 64 | 65 | #ifndef V2F_DEFINED 66 | #define V2F_DEFINED 67 | typedef struct v2f { 68 | float x, y; 69 | } v2f; 70 | #endif 71 | FORCE_INLINE v2f V2F(float x, float y) {v2f v = {x, y}; return v; } 72 | 73 | typedef struct v3f { 74 | union { 75 | struct {float r, g, b; }; 76 | struct {float x, y, z; }; 77 | float values[3]; 78 | }; 79 | } v3f; 80 | FORCE_INLINE v3f V3F(float x, float y, float z) {v3f v = {{{x, y, z}}}; return v; } 81 | 82 | #ifndef V4F_DEFINED 83 | #define V4F_DEFINED 84 | typedef struct v4f { 85 | union { 86 | struct {float r, g, b, a; }; 87 | struct {float x, y, z, w; }; 88 | float values[4]; 89 | }; 90 | } v4f; 91 | #endif 92 | FORCE_INLINE v4f V4F(float x, float y, float z, float w) {v4f v = {{{x, y, z, w}}}; return v; } 93 | 94 | typedef struct bounds2i { 95 | union { 96 | struct { i32 left, top, right, bottom; }; 97 | struct { v2i min, max; }; 98 | }; 99 | } bounds2i; 100 | FORCE_INLINE bounds2i BOUNDS2I(i32 left, i32 top, i32 right, i32 bottom) {bounds2i b = {{{left, top, right, bottom}}}; return b;} 101 | 102 | 103 | typedef struct bounds2f { 104 | union { 105 | struct { float left, top, right, bottom; }; 106 | struct { v2f min, max; }; 107 | }; 108 | } bounds2f; 109 | FORCE_INLINE bounds2f BOUNDS2F(float left, float top, float right, float bottom) {bounds2f b = {{{left, top, right, bottom}}}; return b;} 110 | 111 | typedef struct polygon4v2f { 112 | union { 113 | struct { v2f topleft, topright, bottomleft, bottomright; }; 114 | v2f values[4]; 115 | }; 116 | } polygon4v2f; 117 | 118 | typedef enum corner_enum { 119 | CORNER_TOP_LEFT = 0, 120 | CORNER_TOP_RIGHT = 1, 121 | CORNER_BOTTOM_LEFT = 2, 122 | CORNER_BOTTOM_RIGHT = 3, 123 | } corner_enum; 124 | 125 | 126 | 127 | #pragma pack(pop) 128 | 129 | FORCE_INLINE float v2i_length(v2i v) { return sqrtf((float)SQUARE(v.x) + (float)SQUARE(v.y)); } 130 | FORCE_INLINE float v2f_length(v2f v) { return sqrtf(SQUARE(v.x) + SQUARE(v.y)); } 131 | FORCE_INLINE float v2f_length_squared(v2f v) { return SQUARE(v.x) + SQUARE(v.y); } 132 | 133 | FORCE_INLINE v2f v2f_add(v2f a, v2f b) { v2f result = {a.x + b.x, a.y + b.y }; return result; } 134 | FORCE_INLINE v2i v2i_add(v2i a, v2i b) { v2i result = {a.x + b.x, a.y + b.y }; return result; } 135 | FORCE_INLINE v2f v2f_subtract(v2f a, v2f b) { v2f result = {a.x - b.x, a.y - b.y }; return result; } 136 | FORCE_INLINE v2i v2i_subtract(v2i a, v2i b) { v2i result = {a.x - b.x, a.y - b.y }; return result; } 137 | FORCE_INLINE float v2f_dot(v2f a, v2f b) { return a.x * b.x + a.y * b.y; } 138 | FORCE_INLINE v2f v2f_scale(float scalar, v2f v) { v2f result = {v.x * scalar, v.y * scalar }; return result; } 139 | FORCE_INLINE v2f v2f_lerp(v2f a, v2f b_minus_a, float t) { v2f result = v2f_add(a, v2f_scale(t, b_minus_a)); return result; } 140 | 141 | // prototypes 142 | rect2i clip_rect(rect2i* first, rect2i* second); 143 | bounds2i clip_bounds2i(bounds2i a, bounds2i b); 144 | bounds2f clip_bounds2f(bounds2f a, bounds2f b); 145 | bool is_point_inside_rect2i(rect2i rect, v2i point); 146 | bool is_point_inside_bounds2i(bounds2i bounds, v2i point); 147 | v2i rect2i_center_point(rect2i rect); 148 | v2f rect2f_center_point(rect2f rect); 149 | rect2f rect2f_recanonicalize(rect2f* rect); 150 | bounds2f rect2f_to_bounds(rect2f rect); 151 | rect2f bounds2f_to_rect(bounds2f bounds); 152 | bounds2f bounds2f_encompassing(bounds2f a, bounds2f b); 153 | bool are_bounds2f_overlapping(bounds2f a, bounds2f b); 154 | v2i world_pos_to_pixel_pos(v2f world_pos, float um_per_pixel, i32 level); 155 | i32 tile_pos_from_world_pos(float world_pos, float tile_side); 156 | bounds2i world_bounds_to_tile_bounds(bounds2f* world_bounds, float tile_width, float tile_height, v2f image_pos); 157 | bounds2f tile_bounds_to_world_bounds(bounds2i tile_bounds, float tile_width, float tile_height, v2f image_pos); 158 | bounds2f bounds_from_center_point(v2f center, float r_minus_l, float t_minus_b); 159 | bounds2f bounds_from_pivot_point(v2f pivot, v2f pivot_relative_pos, float r_minus_l, float t_minus_b); 160 | bounds2f bounds_from_points(v2f* points, i32 point_count); 161 | polygon4v2f rotated_rectangle(float width, float height, float rotation); 162 | bounds2i world_bounds_to_pixel_bounds(bounds2f* world_bounds, float mpp_x, float mpp_y); 163 | rect2f pixel_rect_to_world_rect(rect2i pixel_rect, float mpp_x, float mpp_y); 164 | v2f project_point_on_line_segment(v2f point, v2f line_start, v2f line_end, float* t_ptr); 165 | bool v2f_within_bounds(bounds2f bounds, v2f point); 166 | bool v2f_between_points(v2f v, v2f p0, v2f p1); 167 | v2f v2f_average(v2f a, v2f b); 168 | corner_enum get_closest_corner(v2f center_point, v2f p); 169 | v2f get_corner_pos(rect2f rect, corner_enum corner); 170 | 171 | // globals 172 | #if defined(MATHUTILS_IMPL) 173 | #define INIT(...) __VA_ARGS__ 174 | #define extern 175 | #else 176 | #define INIT(...) 177 | #undef extern 178 | #endif 179 | 180 | //... 181 | 182 | #undef INIT 183 | #undef extern 184 | 185 | 186 | #ifdef __cplusplus 187 | } 188 | #endif 189 | 190 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(libisyntax) 3 | 4 | set(CMAKE_C_STANDARD 11) 5 | 6 | if(NOT CMAKE_BUILD_TYPE) 7 | set(CMAKE_BUILD_TYPE Release) 8 | endif() 9 | 10 | message(STATUS "CMAKE_BUILD_TYPE = ${CMAKE_BUILD_TYPE}") 11 | 12 | if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*|arm64.*|ARM64.*)") 13 | set(IS_ARM64 TRUE) 14 | set(ARM_ARCH "arm64") 15 | elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm.*|ARM.*)") 16 | set(IS_ARM TRUE) 17 | set(ARM_ARCH "arm") 18 | endif() 19 | 20 | if(APPLE AND (IS_ARM OR IS_ARM64)) 21 | set(IS_APPLE_SILICON TRUE) 22 | endif() 23 | 24 | if(IS_APPLE_SILICON) 25 | message(STATUS "Detected Apple silicon ${ARM_ARCH} architecture") 26 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -arch ${ARM_ARCH}") 27 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch ${ARM_ARCH}") 28 | # Enable NEON for ARM-based Apple Silicon 29 | add_compile_options(-march=armv8.2-a+fp16+simd) 30 | else() 31 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native") 32 | endif() 33 | 34 | 35 | option(ENABLE_MEMORY_CHECK "Enable memory check with sanitizers (requires DEBUG mode)" OFF) 36 | 37 | if(ENABLE_MEMORY_CHECK) 38 | if(NOT CMAKE_BUILD_TYPE MATCHES Debug) 39 | message(FATAL_ERROR "ENABLE_MEMORY_CHECK is only allowed in Debug build mode") 40 | endif() 41 | 42 | if(APPLE) 43 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O1 -fno-omit-frame-pointer -g") 44 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer") 45 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-omit-frame-pointer") 46 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address") 47 | elseif(UNIX AND NOT APPLE) # Linux 48 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O1 -g") 49 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Og -g") 50 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -g") 51 | endif() 52 | endif() 53 | 54 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}") 55 | 56 | include_directories("${CMAKE_SOURCE_DIR}/src") 57 | include_directories("${CMAKE_SOURCE_DIR}/src/platform") 58 | include_directories("${CMAKE_SOURCE_DIR}/src/utils") 59 | include_directories("${CMAKE_SOURCE_DIR}/src/isyntax") 60 | include_directories("${CMAKE_SOURCE_DIR}/src/third_party") 61 | 62 | set(LIBISYNTAX_COMMON_SOURCE_FILES 63 | src/libisyntax.c 64 | src/isyntax/isyntax.c 65 | src/isyntax/isyntax_reader.c 66 | src/utils/timerutils.c 67 | src/utils/block_allocator.c 68 | src/utils/benaphore.c 69 | src/platform/platform.c 70 | src/platform/work_queue.c 71 | src/third_party/yxml.c 72 | src/third_party/ltalloc.cc 73 | ) 74 | 75 | if (WIN32) 76 | set(LIBISYNTAX_COMMON_SOURCE_FILES ${LIBISYNTAX_COMMON_SOURCE_FILES} src/platform/win32_utils.c) 77 | else() 78 | set(LIBISYNTAX_COMMON_SOURCE_FILES ${LIBISYNTAX_COMMON_SOURCE_FILES} src/platform/linux_utils.c) 79 | endif() 80 | 81 | add_library(isyntax ${LIBISYNTAX_COMMON_SOURCE_FILES}) 82 | 83 | if (WIN32) 84 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static-libgcc -static") 85 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -static-libgcc -static") 86 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS_DEBUG} -static-libgcc -static") 87 | target_link_libraries(isyntax winmm) 88 | else() 89 | find_package(Threads REQUIRED) 90 | target_link_libraries(isyntax Threads::Threads) 91 | endif() 92 | 93 | # Build example program: isyntax_example.c 94 | add_executable(isyntax_example src/examples/isyntax_example.c) 95 | target_link_libraries(isyntax_example isyntax) 96 | 97 | # Build iSyntax to TIFF converter program (depends on LibTIFF library) 98 | find_package(TIFF) 99 | if (NOT TIFF_FOUND) 100 | if (WIN32) 101 | # On Windows, compile-time linking to libtiff is a nightmare to set up (at least for me). 102 | # As a fallback, use dynamic linking at runtime, which is a lesser headache. 103 | message(WARNING "LibTIFF not found - `isyntax-to-tiff` utility will try to link LibTIFF at runtime") 104 | # To access the symbols, pass the directory containing the libtiff sources to CMake like so: 105 | # -DTIFF_INCLUDE_DIR=C:/work/libraries/tiff-4.6.0/libtiff 106 | include_directories(${TIFF_INCLUDE_DIR}) 107 | add_executable(isyntax-to-tiff src/examples/isyntax_to_tiff.c) 108 | target_compile_definitions(isyntax-to-tiff PRIVATE LINK_LIBTIFF_AT_RUNTIME=1) 109 | target_link_libraries(isyntax-to-tiff isyntax winmm) 110 | else() 111 | message(WARNING "LibTIFF not found") 112 | message(WARNING "Will not compile `isyntax-to-tiff` utility") 113 | endif() 114 | else() 115 | include_directories(${TIFF_INCLUDE_DIR}) 116 | add_executable(isyntax-to-tiff src/examples/isyntax_to_tiff.c) 117 | target_link_libraries(isyntax-to-tiff isyntax ${TIFF_LIBRARIES}) 118 | endif() 119 | 120 | # Build example program: isyntax_dirwalk.c 121 | add_executable(isyntax-dirwalk src/examples/isyntax_dirwalk.c) 122 | target_link_libraries(isyntax-dirwalk isyntax) 123 | 124 | # TODO(avirodov): Consider moving testing to its own test/CMakeLists. This will require moving the library building 125 | # to src/CMakeLists to avoid circular deps. 126 | 127 | # Note: checking if running in test mode to avoid downloading a large test file for regular builds. 128 | option(BUILD_TESTING "Build tests" OFF) 129 | message(STATUS "BUILD_TESTING = ${BUILD_TESTING}") 130 | if(BUILD_TESTING) 131 | include(CTest) 132 | add_test(NAME smoke_example_runs_no_args 133 | COMMAND isyntax_example) 134 | 135 | # Thank you https://gitlab.com/BioimageInformaticsGroup/openphi 136 | if (NOT EXISTS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/testslide.isyntax) 137 | file(DOWNLOAD https://zenodo.org/record/5037046/files/testslide.isyntax?download=1 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/testslide.isyntax SHOW_PROGRESS) 138 | else() 139 | message(STATUS "Found test slide (no need to download): ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/testslide.isyntax") 140 | endif() 141 | 142 | # Test that we can show levels and that number of tiles shown is as expected for this test tile. 143 | add_test(NAME smoke_example_runs_with_test_slide_showing_levels 144 | COMMAND isyntax_example ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/testslide.isyntax) 145 | set_tests_properties(smoke_example_runs_with_test_slide_showing_levels 146 | PROPERTIES PASS_REGULAR_EXPRESSION "width.*=256") 147 | set_tests_properties(smoke_example_runs_with_test_slide_showing_levels 148 | PROPERTIES PASS_REGULAR_EXPRESSION "height.*=384") 149 | 150 | # Test that we can produce a tile png. 151 | add_test(NAME smoke_example_runs_with_test_slide_producing_output 152 | COMMAND isyntax_example ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/testslide.isyntax 3 5 10 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/output_smoke_example_runs_with_test_slide_producing_output.png) 153 | 154 | # Test that the tile png was indeed produced. 155 | add_test(NAME smoke_example_runs_with_test_slide_produced_output 156 | COMMAND ${CMAKE_COMMAND} -E cat ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/output_smoke_example_runs_with_test_slide_producing_output.png) 157 | set_tests_properties(smoke_example_runs_with_test_slide_produced_output PROPERTIES DEPENDS smoke_example_runs_with_test_slide_producing_output) 158 | 159 | # Regression test that the produced tile pixels did not change from expected. 160 | add_test(NAME regression_example_tile_3_5_10_pixel_check 161 | COMMAND ${CMAKE_COMMAND} -E compare_files ../test/expected_output/testslide_example_tile_3_5_10.png ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/output_smoke_example_runs_with_test_slide_producing_output.png) 162 | set_tests_properties(regression_example_tile_3_5_10_pixel_check PROPERTIES DEPENDS smoke_example_runs_with_test_slide_producing_output) 163 | 164 | if(NOT(APPLE)) 165 | # TODO: fix this test on macOS: fatal error: 'threads.h' file not found 166 | add_executable(thread_test test/thread_test.c) 167 | target_link_libraries(thread_test isyntax) 168 | add_test(NAME smoke_thread_test 169 | COMMAND thread_test) 170 | endif() 171 | 172 | 173 | endif() # if(BUILD_TESTING) 174 | 175 | 176 | -------------------------------------------------------------------------------- /src/platform/intrinsics.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "common.h" 31 | 32 | // https://stackoverflow.com/questions/11228855/header-files-for-x86-simd-intrinsics 33 | #if defined(_MSC_VER) 34 | /* Microsoft C/C++-compatible compiler */ 35 | #include 36 | #elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) 37 | /* GCC-compatible compiler, targeting x86/x86-64 */ 38 | #include 39 | #elif defined(__GNUC__) && defined(__ARM_NEON__) 40 | /* GCC-compatible compiler, targeting ARM with NEON */ 41 | #include 42 | #elif defined(__GNUC__) && defined(__IWMMXT__) 43 | /* GCC-compatible compiler, targeting ARM with WMMX */ 44 | #include 45 | #elif (defined(__GNUC__) || defined(__xlC__)) && (defined(__VEC__) || defined(__ALTIVEC__)) 46 | /* XLC or GCC-compatible compiler, targeting PowerPC with VMX/VSX */ 47 | #include 48 | #elif defined(__GNUC__) && defined(__SPE__) 49 | /* GCC-compatible compiler, targeting PowerPC with SPE */ 50 | #include 51 | #endif 52 | 53 | #if WINDOWS 54 | #define write_barrier do { _WriteBarrier(); _mm_sfence(); } while (0) 55 | #define read_barrier _ReadBarrier() 56 | 57 | static inline i32 atomic_increment(volatile i32* x) { 58 | return InterlockedIncrement((volatile long*)x); 59 | } 60 | 61 | static inline i32 atomic_decrement(volatile i32* x) { 62 | return InterlockedDecrement((volatile long*)x); 63 | } 64 | 65 | static inline i32 atomic_add(volatile i32* x, i32 amount) { 66 | return InterlockedAdd((volatile long*)x, (long)amount); 67 | } 68 | 69 | static inline i32 atomic_subtract(volatile i32* x, i32 amount) { 70 | return InterlockedAdd((volatile long*)x, (long)(-amount)); 71 | } 72 | 73 | static inline bool atomic_compare_exchange(volatile i32* destination, i32 exchange, i32 comparand) { 74 | i32 read_value = InterlockedCompareExchange((volatile long*)destination, exchange, comparand); 75 | return (read_value == comparand); 76 | } 77 | 78 | static inline u32 bit_scan_forward(u32 x) { 79 | unsigned long first_bit = 0; 80 | _BitScanForward(&first_bit, x); 81 | return (u32) first_bit; 82 | } 83 | 84 | #elif APPLE 85 | #define OSATOMIC_USE_INLINED 1 86 | #include 87 | 88 | #define write_barrier 89 | #define read_barrier 90 | 91 | static inline i32 atomic_increment(volatile i32* x) { 92 | return OSAtomicIncrement32(x); 93 | } 94 | 95 | static inline i32 atomic_decrement(volatile i32* x) { 96 | return OSAtomicDecrement32(x); 97 | } 98 | 99 | static inline i32 atomic_add(volatile i32* x, i32 amount) { 100 | return OSAtomicAdd32(amount, x); 101 | } 102 | 103 | static inline i32 atomic_subtract(volatile i32* x, i32 amount) { 104 | return OSAtomicAdd32(-amount, x); 105 | } 106 | 107 | static inline bool atomic_compare_exchange(volatile i32* destination, i32 exchange, i32 comparand) { 108 | bool result = OSAtomicCompareAndSwap32(comparand, exchange, destination); 109 | return result; 110 | } 111 | 112 | static inline u32 bit_scan_forward(u32 x) { 113 | return __builtin_ctz(x); 114 | } 115 | 116 | #else 117 | //TODO: implement 118 | #define write_barrier 119 | #define read_barrier 120 | 121 | static inline i32 atomic_increment(volatile i32* x) { 122 | return __sync_add_and_fetch(x, 1); 123 | } 124 | 125 | static inline i32 atomic_decrement(volatile i32* x) { 126 | return __sync_sub_and_fetch(x, 1); 127 | } 128 | 129 | static inline i32 atomic_add(volatile i32* x, i32 amount) { 130 | return __sync_add_and_fetch(x, amount); 131 | } 132 | 133 | static inline i32 atomic_subtract(volatile i32* x, i32 amount) { 134 | return __sync_sub_and_fetch(x, amount); 135 | } 136 | 137 | static inline bool atomic_compare_exchange(volatile i32* destination, i32 exchange, i32 comparand) { 138 | i32 read_value = __sync_val_compare_and_swap(destination, comparand, exchange); 139 | return (read_value == comparand); 140 | } 141 | 142 | static inline u32 atomic_or(volatile u32* x, u32 mask) { 143 | return __sync_or_and_fetch(x, mask); 144 | } 145 | 146 | static inline u32 bit_scan_forward(u32 x) { 147 | return _bit_scan_forward(x); 148 | } 149 | 150 | #endif 151 | 152 | // see: 153 | // https://stackoverflow.com/questions/41770887/cross-platform-definition-of-byteswap-uint64-and-byteswap-ulong 154 | // byte swap operations adapted from this code: 155 | // https://github.com/google/cityhash/blob/8af9b8c2b889d80c22d6bc26ba0df1afb79a30db/src/city.cc#L50 156 | // License information copied in below: 157 | 158 | // Copyright (c) 2011 Google, Inc. 159 | // 160 | // Permission is hereby granted, free of charge, to any person obtaining a copy 161 | // of this software and associated documentation files (the "Software"), to deal 162 | // in the Software without restriction, including without limitation the rights 163 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 164 | // copies of the Software, and to permit persons to whom the Software is 165 | // furnished to do so, subject to the following conditions: 166 | // 167 | // The above copyright notice and this permission notice shall be included in 168 | // all copies or substantial portions of the Software. 169 | // 170 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 171 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 172 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 173 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 174 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 175 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 176 | // THE SOFTWARE. 177 | // 178 | // CityHash, by Geoff Pike and Jyrki Alakuijala 179 | 180 | #ifdef __GNUC__ 181 | 182 | #define bswap_16(x) __builtin_bswap16(x) 183 | #define bswap_32(x) __builtin_bswap32(x) 184 | #define bswap_64(x) __builtin_bswap64(x) 185 | 186 | #elif _MSC_VER 187 | 188 | #include 189 | #define bswap_16(x) _byteswap_ushort(x) 190 | #define bswap_32(x) _byteswap_ulong(x) 191 | #define bswap_64(x) _byteswap_uint64(x) 192 | 193 | #elif defined(__APPLE__) 194 | 195 | // Mac OS X / Darwin features 196 | #include 197 | #define bswap_16(x) OSSwapInt16(x) 198 | #define bswap_32(x) OSSwapInt32(x) 199 | #define bswap_64(x) OSSwapInt64(x) 200 | 201 | #elif defined(__sun) || defined(sun) 202 | 203 | #include 204 | #define bswap_16(x) BSWAP_16(x) 205 | #define bswap_32(x) BSWAP_32(x) 206 | #define bswap_64(x) BSWAP_64(x) 207 | 208 | #elif defined(__FreeBSD__) 209 | 210 | #include 211 | #define bswap_16(x) bswap16(x) 212 | #define bswap_32(x) bswap32(x) 213 | #define bswap_64(x) bswap64(x) 214 | 215 | #elif defined(__OpenBSD__) 216 | 217 | #include 218 | #define bswap_32(x) swap16(x) 219 | #define bswap_32(x) swap32(x) 220 | #define bswap_64(x) swap64(x) 221 | 222 | #elif defined(__NetBSD__) 223 | 224 | #include 225 | #include 226 | #if defined(__BSWAP_RENAME) && !defined(__bswap_32) 227 | #define bswap_16(x) bswap16(x) 228 | #define bswap_32(x) bswap32(x) 229 | #define bswap_64(x) bswap64(x) 230 | #endif 231 | 232 | #else 233 | 234 | #include 235 | 236 | #endif 237 | 238 | static inline u16 maybe_swap_16(u16 x, bool is_big_endian) { 239 | return is_big_endian ? bswap_16(x) : x; 240 | } 241 | 242 | static inline u32 maybe_swap_32(u32 x, bool is_big_endian) { 243 | return is_big_endian ? bswap_32(x) : x; 244 | } 245 | 246 | static inline u64 maybe_swap_64(u64 x, bool is_big_endian) { 247 | return is_big_endian ? bswap_64(x) : x; 248 | } 249 | 250 | #if APPLE_ARM 251 | static inline i32 popcount(u32 x) { 252 | return __builtin_popcount(x); 253 | } 254 | #elif COMPILER_MSVC 255 | static inline i32 popcount(u32 x) { 256 | return __popcnt(x); 257 | } 258 | #else 259 | static inline i32 popcount(u32 x) { 260 | return _popcnt32(x); 261 | } 262 | #endif 263 | -------------------------------------------------------------------------------- /src/platform/work_queue.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #define WORK_QUEUE_IMPL 29 | #include "work_queue.h" 30 | #include "platform.h" 31 | #include "intrinsics.h" 32 | 33 | #if defined(_WIN32) 34 | #include 35 | #else 36 | #include 37 | #endif 38 | 39 | work_queue_t work_queue_create(const char* semaphore_name, i32 entry_count) { 40 | work_queue_t queue = {0}; 41 | 42 | i32 semaphore_initial_count = 0; 43 | #if WINDOWS 44 | LONG maximum_count = 1e6; // realistically, we'd only get up to the number of worker threads, though 45 | queue.semaphore = CreateSemaphoreExA(0, semaphore_initial_count, maximum_count, semaphore_name, 0, SEMAPHORE_ALL_ACCESS); 46 | #else 47 | queue.semaphore = sem_open(semaphore_name, O_CREAT, 0644, semaphore_initial_count); 48 | #endif 49 | queue.entry_count = entry_count + 1; // add safety margin to detect when queue is about to overflow 50 | queue.entries = calloc(1, (entry_count + 1) * sizeof(work_queue_entry_t)); 51 | return queue; 52 | } 53 | 54 | void work_queue_destroy(work_queue_t* queue) { 55 | if (queue->entries) { 56 | free(queue->entries); 57 | queue->entries = NULL; 58 | } 59 | #if WINDOWS 60 | CloseHandle(queue->semaphore); 61 | #else 62 | sem_close(queue->semaphore); 63 | #endif 64 | queue->semaphore = NULL; 65 | } 66 | 67 | i32 work_queue_get_entry_count(work_queue_t* queue) { 68 | i32 count = queue->next_entry_to_submit - queue->next_entry_to_execute; 69 | while (count < 0) { 70 | count += queue->entry_count; 71 | } 72 | return count; 73 | } 74 | 75 | // TODO: add optional refcount increment 76 | bool work_queue_submit(work_queue_t* queue, work_queue_callback_t callback, u32 task_identifier, void* userdata, size_t userdata_size) { 77 | if (!queue) { 78 | fatal_error("work_queue_add_entry(): queue is NULL"); 79 | } 80 | if (userdata_size > sizeof(((work_queue_entry_t*)0)->userdata)) { 81 | fatal_error("work_queue_add_entry(): userdata_size overflows available space"); 82 | } 83 | for (i32 tries = 0; tries < 1000; ++tries) { 84 | // Circular FIFO buffer 85 | i32 entry_to_submit = queue->next_entry_to_submit; 86 | i32 new_next_entry_to_submit = (queue->next_entry_to_submit + 1) % queue->entry_count; 87 | if (new_next_entry_to_submit == queue->next_entry_to_execute) { 88 | // TODO: fix multithreading problem: completion queue overflowing 89 | console_print_error("Warning: work queue is overflowing - job is cancelled\n"); 90 | return false; 91 | } 92 | 93 | bool succeeded = atomic_compare_exchange(&queue->next_entry_to_submit, 94 | new_next_entry_to_submit, entry_to_submit); 95 | if (succeeded) { 96 | // console_print("exhange succeeded\n"); 97 | work_queue_entry_t* entry = queue->entries + entry_to_submit; 98 | *entry = (work_queue_entry_t){ .callback = callback, .task_identifier = task_identifier }; 99 | if (userdata_size > 0) { 100 | ASSERT(userdata); 101 | memcpy(entry->userdata, userdata, userdata_size); 102 | } 103 | write_barrier; 104 | entry->is_valid = true; 105 | write_barrier; 106 | atomic_increment(&queue->completion_goal); 107 | atomic_increment(&queue->start_goal); 108 | // queue->next_entry_to_submit = new_next_entry_to_submit; 109 | platform_semaphore_post(queue->semaphore); 110 | return true; 111 | } else { 112 | if (tries > 5) { 113 | console_print_error("exchange failed, retrying (try #%d)\n", tries); 114 | } 115 | continue; 116 | } 117 | 118 | } 119 | return false; 120 | } 121 | 122 | bool work_queue_submit_task(work_queue_t* queue, work_queue_callback_t callback, void* userdata, size_t userdata_size) { 123 | ASSERT(callback); 124 | return work_queue_submit(queue, callback, 0, userdata, userdata_size); 125 | } 126 | 127 | bool work_queue_submit_notification(work_queue_t* queue, u32 task_identifier, void* userdata, size_t userdata_size) { 128 | return work_queue_submit(queue, NULL, task_identifier, userdata, userdata_size); 129 | } 130 | 131 | work_queue_entry_t work_queue_get_next_entry(work_queue_t* queue) { 132 | work_queue_entry_t result = {0}; 133 | 134 | i32 entry_to_execute = queue->next_entry_to_execute; 135 | i32 new_next_entry_to_execute = (entry_to_execute + 1) % queue->entry_count; 136 | 137 | // don't even try to execute a task if it is not yet submitted, or not yet fully submitted 138 | if ((entry_to_execute != queue->next_entry_to_submit) && (queue->entries[entry_to_execute].is_valid)) { 139 | bool succeeded = atomic_compare_exchange(&queue->next_entry_to_execute, 140 | new_next_entry_to_execute, entry_to_execute); 141 | if (succeeded) { 142 | // We have dibs to execute this task! 143 | result = queue->entries[entry_to_execute]; 144 | queue->entries[entry_to_execute].is_valid = false; // discourage competing threads (maybe not needed?) 145 | if (result.callback == NULL && result.task_identifier == 0) { 146 | console_print_error("Warning: encountered a work entry with a missing callback routine and/or task identifier (is this intended)?\n"); 147 | } 148 | result.is_valid = true; 149 | read_barrier; 150 | } 151 | } 152 | 153 | return result; 154 | } 155 | 156 | void work_queue_mark_entry_completed(work_queue_t* queue) { 157 | atomic_increment(&queue->completion_count); 158 | } 159 | 160 | bool work_queue_do_work(work_queue_t* queue, int logical_thread_index) { 161 | work_queue_entry_t entry = work_queue_get_next_entry(queue); 162 | if (entry.is_valid) { 163 | atomic_decrement(&global_worker_thread_idle_count); 164 | atomic_increment(&queue->start_count); 165 | ASSERT(entry.callback); 166 | if (entry.callback) { 167 | // Simple way to keep track if we are executing a 'nested' task (i.e. executing a job while waiting to continue another job) 168 | ++work_queue_call_depth; 169 | 170 | // Copy the user data (arguments for the call) onto the stack 171 | u8* userdata = alloca(sizeof(entry.userdata)); 172 | memcpy(userdata, entry.userdata, sizeof(entry.userdata)); 173 | 174 | // Ensure all the memory allocated on the thread's temp_arena will be released when the task completes 175 | temp_memory_t temp = begin_temp_memory_on_local_thread(); 176 | 177 | // Execute the task 178 | entry.callback(logical_thread_index, userdata); 179 | 180 | release_temp_memory(&temp); 181 | --work_queue_call_depth; 182 | } 183 | work_queue_mark_entry_completed(queue); 184 | atomic_increment(&global_worker_thread_idle_count); 185 | } 186 | return entry.is_valid; 187 | } 188 | 189 | 190 | bool work_queue_is_work_in_progress(work_queue_t* queue) { 191 | // If we are checking the global work queue while running a task from that same queue, then we only want to know 192 | // whether any OTHER tasks are running. So in that case we need to subtract the call depth. 193 | i32 call_depth = (queue == &global_work_queue) ? work_queue_call_depth : 0; 194 | bool result = (queue->completion_goal - call_depth > queue->completion_count); 195 | return result; 196 | } 197 | 198 | bool work_queue_is_work_waiting_to_start(work_queue_t* queue) { 199 | bool result = (queue->start_goal > queue->start_count); 200 | return result; 201 | } 202 | 203 | void dummy_work_queue_callback(int logical_thread_index, void* userdata) {} 204 | 205 | //#define TEST_THREAD_QUEUE 206 | #ifdef TEST_THREAD_QUEUE 207 | void echo_task_completed(int logical_thread_index, void* userdata) { 208 | console_print("thread %d completed: %s\n", logical_thread_index, (char*) userdata); 209 | } 210 | 211 | void echo_task(int logical_thread_index, void* userdata) { 212 | console_print("thread %d: %s\n", logical_thread_index, (char*) userdata); 213 | 214 | work_queue_submit_task(&global_completion_queue, echo_task_completed, userdata, strlen(userdata)+1); 215 | } 216 | #endif 217 | 218 | void test_multithreading_work_queue() { 219 | #ifdef TEST_THREAD_QUEUE 220 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"NULL entry", 11); 221 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 0", 9); 222 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 1", 9); 223 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 2", 9); 224 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 3", 9); 225 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 4", 9); 226 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 5", 9); 227 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 6", 9); 228 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 7", 9); 229 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 8", 9); 230 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 9", 9); 231 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 10", 10); 232 | work_queue_submit_task(&global_work_queue, echo_task, (void*)"string 11", 10); 233 | 234 | while (work_queue_is_work_in_progress(&global_work_queue) || work_queue_is_work_in_progress((&global_completion_queue))) { 235 | work_queue_do_work(&global_completion_queue, 0); 236 | } 237 | #endif 238 | } 239 | -------------------------------------------------------------------------------- /src/platform/win32_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2023, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "common.h" 29 | #include "win32_utils.h" 30 | 31 | wchar_t* win32_string_widen(const char* s, size_t len, wchar_t* buffer) { 32 | int characters_written = MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, len); 33 | if (characters_written > 0) { 34 | int last_character = MIN(characters_written, ((int)len)-1); 35 | buffer[last_character] = '\0'; 36 | } else { 37 | win32_diagnostic("MultiByteToWideChar"); 38 | buffer[0] = '\0'; 39 | } 40 | return buffer; 41 | } 42 | 43 | char* win32_string_narrow(wchar_t* s, char* buffer, size_t buffer_size) { 44 | int bytes_written = WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, buffer_size, NULL, NULL); 45 | if (bytes_written > 0) { 46 | ASSERT(bytes_written < buffer_size); 47 | int last_byte = MIN(bytes_written, ((int)buffer_size)-1); 48 | buffer[last_byte] = '\0'; 49 | } else { 50 | win32_diagnostic("WideCharToMultiByte"); 51 | buffer[0] = '\0'; 52 | } 53 | return buffer; 54 | } 55 | 56 | void win32_diagnostic(const char* prefix) { 57 | DWORD error_id = GetLastError(); 58 | char* message_buffer; 59 | /*size_t size = */FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 60 | NULL, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message_buffer, 0, NULL); 61 | console_print("%s: (error code 0x%x) %s\n", prefix, (u32)error_id, message_buffer); 62 | LocalFree(message_buffer); 63 | } 64 | 65 | void win32_diagnostic_verbose(const char* prefix) { 66 | DWORD error_id = GetLastError(); 67 | char* message_buffer; 68 | /*size_t size = */FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 69 | NULL, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message_buffer, 0, NULL); 70 | console_print_verbose("%s: (error code 0x%x) %s\n", prefix, (u32)error_id, message_buffer); 71 | LocalFree(message_buffer); 72 | } 73 | 74 | HANDLE win32_open_overlapped_file_handle(const char* filename) { 75 | // NOTE: Using the FILE_FLAG_NO_BUFFERING flag *might* be faster, but I am not actually noticing a speed increase, 76 | // so I am keeping it turned off for now. 77 | size_t filename_len = strlen(filename) + 1; 78 | wchar_t* wide_filename = win32_string_widen(filename, filename_len, (wchar_t*) alloca(2 * filename_len)); 79 | HANDLE handle = CreateFileW(wide_filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 80 | FILE_ATTRIBUTE_NORMAL | /*FILE_FLAG_SEQUENTIAL_SCAN |*/ 81 | /*FILE_FLAG_NO_BUFFERING |*/ FILE_FLAG_OVERLAPPED, 82 | NULL); 83 | return handle; 84 | } 85 | 86 | file_handle_t open_file_handle_for_simultaneous_access(const char* filename) { 87 | return win32_open_overlapped_file_handle(filename); 88 | } 89 | 90 | void file_handle_close(file_handle_t file_handle) { 91 | if (file_handle) { 92 | CloseHandle(file_handle); 93 | } 94 | } 95 | 96 | size_t win32_overlapped_read(thread_memory_t* thread_memory, HANDLE file_handle, void* dest, u32 read_size, i64 offset) { 97 | // We align reads to 4K boundaries, so that file handles can be used with the FILE_FLAG_NO_BUFFERING flag. 98 | // See: https://docs.microsoft.com/en-us/windows/win32/fileio/file-buffering 99 | i64 aligned_offset = offset & ~(KILOBYTES(4)-1); 100 | i64 align_delta = offset - aligned_offset; 101 | ASSERT(align_delta >= 0); 102 | 103 | i64 end_byte = offset + read_size; 104 | i64 raw_read_size = end_byte - aligned_offset; 105 | ASSERT(raw_read_size >= 0); 106 | 107 | i64 bytes_to_read_in_last_sector = raw_read_size % KILOBYTES(4); 108 | if (bytes_to_read_in_last_sector > 0) { 109 | raw_read_size += KILOBYTES(4) - bytes_to_read_in_last_sector; 110 | } 111 | 112 | temp_memory_t temp_memory = begin_temp_memory(&thread_memory->temp_arena); 113 | i64 bytes_left_in_temp_memory = arena_get_bytes_left(&thread_memory->temp_arena); 114 | bool need_free_temp_dest; 115 | u8* temp_dest; 116 | if (bytes_left_in_temp_memory >= raw_read_size) { 117 | temp_dest = (u8*)arena_push_size(&thread_memory->temp_arena, raw_read_size); 118 | need_free_temp_dest = false; 119 | } else { 120 | temp_dest = (u8*)malloc(raw_read_size); 121 | need_free_temp_dest = true; 122 | } 123 | 124 | // To submit an async I/O request on Win32, we need to fill in an OVERLAPPED structure with the 125 | // offset in the file where we want to do the read operation 126 | LARGE_INTEGER offset_ = {.QuadPart = (i64)aligned_offset}; 127 | OVERLAPPED overlapped = {0}; 128 | overlapped.Offset = offset_.LowPart; 129 | overlapped.OffsetHigh = (DWORD)offset_.HighPart; 130 | overlapped.hEvent = thread_memory->async_io_events[0]; 131 | ResetEvent(thread_memory->async_io_events[0]); // reset the event to unsignaled state 132 | 133 | if (!ReadFile(file_handle, temp_dest, raw_read_size, NULL, &overlapped)) { 134 | DWORD error = GetLastError(); 135 | if (error != ERROR_IO_PENDING) { 136 | win32_diagnostic("ReadFile"); 137 | if (need_free_temp_dest) { 138 | free(temp_dest); 139 | } 140 | release_temp_memory(&temp_memory); 141 | return 0; 142 | } 143 | } 144 | 145 | // Wait for the result of the I/O operation (blocking, because we specify bWait=TRUE) 146 | DWORD bytes_read = 0; 147 | if (!GetOverlappedResult(file_handle, &overlapped, &bytes_read, TRUE)) { 148 | win32_diagnostic("GetOverlappedResult"); 149 | } 150 | 151 | // This should not be strictly necessary, but do it just in case GetOverlappedResult exits early (paranoia) 152 | // NOTE: this seems to sometimes return an error 0x6 (handle invalid) if used in quick successive calls... (?) 153 | // if(WaitForSingleObject(overlapped.hEvent, INFINITE) != WAIT_OBJECT_0) { 154 | // win32_diagnostic("WaitForSingleObject"); 155 | // } 156 | 157 | memcpy(dest, temp_dest + align_delta, read_size); 158 | 159 | if (need_free_temp_dest) { 160 | free(temp_dest); 161 | } 162 | release_temp_memory(&temp_memory); 163 | return read_size; 164 | } 165 | 166 | size_t file_handle_read_at_offset(void* dest, file_handle_t file_handle, u64 offset, size_t bytes_to_read) { 167 | size_t bytes_read = win32_overlapped_read(local_thread_memory, file_handle, dest, bytes_to_read, offset); 168 | return bytes_read; 169 | } 170 | 171 | int platform_stat(const char* filename, struct stat* st) { 172 | size_t filename_len = strlen(filename) + 1; 173 | wchar_t* wide_filename = win32_string_widen(filename, filename_len, (wchar_t*) alloca(2 * filename_len)); 174 | return _wstat64(wide_filename, st); 175 | } 176 | 177 | file_stream_t file_stream_open_for_reading(const char* filename) { 178 | // console_print_verbose("Attempting CreateFile() for reading...\n"); 179 | 180 | size_t filename_len = strlen(filename) + 1; 181 | wchar_t* wide_filename = win32_string_widen(filename, filename_len, (wchar_t*) alloca(2 * filename_len)); 182 | HANDLE handle = CreateFileW(wide_filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 183 | FILE_ATTRIBUTE_NORMAL /* | FILE_FLAG_SEQUENTIAL_SCAN */ 184 | /*| FILE_FLAG_NO_BUFFERING |*/ /* | FILE_FLAG_OVERLAPPED*/, 185 | NULL); 186 | if (handle == INVALID_HANDLE_VALUE) { 187 | win32_diagnostic_verbose("CreateFile"); 188 | return 0; 189 | } else { 190 | return handle; 191 | } 192 | } 193 | 194 | file_stream_t file_stream_open_for_writing(const char* filename) { 195 | // console_print_verbose("Attempting CreateFile()...\n"); 196 | 197 | size_t filename_len = strlen(filename) + 1; 198 | wchar_t* wide_filename = win32_string_widen(filename, filename_len, (wchar_t*) alloca(2 * filename_len)); 199 | HANDLE handle = CreateFileW(wide_filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 200 | FILE_ATTRIBUTE_NORMAL /* | FILE_FLAG_SEQUENTIAL_SCAN */ 201 | /*| FILE_FLAG_NO_BUFFERING |*/ /* | FILE_FLAG_OVERLAPPED*/, 202 | NULL); 203 | if (handle == INVALID_HANDLE_VALUE) { 204 | win32_diagnostic("CreateFile"); 205 | return 0; 206 | } else { 207 | return handle; 208 | } 209 | } 210 | 211 | i64 file_stream_read(void* dest, size_t bytes_to_read, file_stream_t file_stream) { 212 | DWORD bytes_read; 213 | // console_print_verbose("Attempting ReadFile()...\n"); 214 | // OVERLAPPED overlapped = {}; 215 | if (!ReadFile(file_stream, dest, bytes_to_read, &bytes_read, NULL)) { 216 | win32_diagnostic("ReadFile"); 217 | return 0; 218 | } else { 219 | // console_print_verbose("ReadFile(): %d bytes read\n", bytes_read); 220 | return (i64)bytes_read; 221 | } 222 | } 223 | 224 | void file_stream_write(void* source, size_t bytes_to_write, file_stream_t file_stream) { 225 | DWORD bytes_written; 226 | if (!WriteFile(file_stream, source, bytes_to_write, &bytes_written, NULL)) { 227 | win32_diagnostic("WriteFile"); 228 | } 229 | } 230 | 231 | i64 file_stream_get_filesize(file_stream_t file_stream) { 232 | LARGE_INTEGER filesize = {0}; 233 | if (!GetFileSizeEx(file_stream, &filesize)) { 234 | win32_diagnostic("GetFileSizeEx"); 235 | } 236 | return filesize.QuadPart; 237 | } 238 | 239 | i64 file_stream_get_pos(file_stream_t file_stream) { 240 | LARGE_INTEGER file_position = {0}; 241 | LARGE_INTEGER distance_to_move = {0}; 242 | if (!SetFilePointerEx(file_stream, distance_to_move, &file_position, FILE_CURRENT)) { 243 | win32_diagnostic("SetFilePointerEx"); 244 | } 245 | return file_position.QuadPart; 246 | } 247 | 248 | bool file_stream_set_pos(file_stream_t file_stream, i64 offset) { 249 | LARGE_INTEGER new_file_pointer = {0}; 250 | new_file_pointer.QuadPart = offset; 251 | if (!SetFilePointerEx(file_stream, new_file_pointer, NULL, FILE_BEGIN)) { 252 | win32_diagnostic("SetFilePointerEx"); 253 | return false; 254 | } else { 255 | return true; 256 | } 257 | } 258 | 259 | void file_stream_close(file_stream_t file_stream) { 260 | if (!CloseHandle(file_stream)) { 261 | win32_diagnostic("CloseHandle"); 262 | } 263 | } 264 | 265 | -------------------------------------------------------------------------------- /src/utils/mathutils.c: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "common.h" 29 | #define MATHUTILS_IMPL 30 | #include "mathutils.h" 31 | 32 | #include "float.h" 33 | 34 | rect2i clip_rect(rect2i* first, rect2i* second) { 35 | i32 x0 = MAX(first->x, second->x); 36 | i32 y0 = MAX(first->y, second->y); 37 | i32 x1 = MIN(first->x + first->w, second->x + second->w); 38 | i32 y1 = MIN(first->y + first->h, second->y + second->h); 39 | rect2i result = { 40 | .x = x0, 41 | .y = y0, 42 | .w = x1 - x0, 43 | .h = y1 - y0, 44 | }; 45 | return result; 46 | } 47 | 48 | bounds2i clip_bounds2i(bounds2i a, bounds2i b) { 49 | bounds2i result = {0}; 50 | result.left = MIN(b.right, MAX(a.left, b.left)); 51 | result.top = MIN(b.bottom, MAX(a.top, b.top)); 52 | result.right = MAX(b.left, MIN(a.right, b.right)); 53 | result.bottom = MAX(b.top, MIN(a.bottom, b.bottom)); 54 | return result; 55 | } 56 | 57 | bounds2f clip_bounds2f(bounds2f a, bounds2f b) { 58 | bounds2f result = {0}; 59 | result.left = MIN(b.right, MAX(a.left, b.left)); 60 | result.top = MIN(b.bottom, MAX(a.top, b.top)); 61 | result.right = MAX(b.left, MIN(a.right, b.right)); 62 | result.bottom = MAX(b.top, MIN(a.bottom, b.bottom)); 63 | return result; 64 | } 65 | 66 | bool is_point_inside_rect2i(rect2i rect, v2i point) { 67 | bool result = true; 68 | if (point.x < rect.x || point.x >= (rect.x + rect.w) || point.y < rect.y || point.y >= (rect.y + rect.h)) { 69 | result = false; 70 | } 71 | return result; 72 | } 73 | 74 | bool is_point_inside_bounds2i(bounds2i bounds, v2i point) { 75 | bool result = true; 76 | if (point.x < bounds.min.x || point.x >= bounds.max.x || point.y < bounds.min.y || point.y >= bounds.max.y) { 77 | result = false; 78 | } 79 | return result; 80 | } 81 | 82 | v2i rect2i_center_point(rect2i rect) { 83 | v2i result = { 84 | .x = rect.x + rect.w / 2, 85 | .y = rect.y + rect.h / 2, 86 | }; 87 | return result; 88 | } 89 | 90 | v2f rect2f_center_point(rect2f rect) { 91 | v2f result = { 92 | .x = rect.x + rect.w * 0.5f, 93 | .y = rect.y + rect.h * 0.5f, 94 | }; 95 | return result; 96 | } 97 | 98 | // reorient a rect with possible negative width and/or height 99 | rect2f rect2f_recanonicalize(rect2f* rect) { 100 | rect2f result = {0}; 101 | if (rect->w >= 0.0f) { 102 | result.x = rect->x; 103 | result.w = rect->w; 104 | } else { 105 | result.x = rect->x + rect->w; // negative, so move coordinate left 106 | result.w = -rect->w; 107 | } 108 | if (rect->h >= 0.0f) { 109 | result.y = rect->y; 110 | result.h = rect->h; 111 | } else { 112 | result.y = rect->y + rect->h; // negative, so move coordinate to top 113 | result.h = -rect->h; 114 | } 115 | return result; 116 | } 117 | 118 | bounds2f rect2f_to_bounds(rect2f rect) { 119 | bounds2f result = { 120 | .left = rect.x, 121 | .top = rect.y, 122 | .right = rect.x + rect.w, 123 | .bottom = rect.y + rect.h 124 | }; 125 | return result; 126 | } 127 | 128 | rect2f bounds2f_to_rect(bounds2f bounds) { 129 | rect2f result = { 130 | .x = bounds.left, 131 | .y = bounds.top, 132 | .w = bounds.right - bounds.left, 133 | .h = bounds.bottom - bounds.top 134 | }; 135 | return result; 136 | } 137 | 138 | bounds2f bounds2f_encompassing(bounds2f a, bounds2f b) { 139 | bounds2f result = { 140 | .left = MIN(a.left, b.left), 141 | .top = MIN(a.top, b.top), 142 | .right = MAX(a.right, b.right), 143 | .bottom = MAX(a.bottom, b.bottom) 144 | }; 145 | return result; 146 | } 147 | 148 | bool are_bounds2f_overlapping(bounds2f a, bounds2f b) { 149 | bool result = (a.min.x <= b.max.x && a.max.x >= b.min.x) && (a.min.y <= b.max.y && a.max.y >= b.min.y); 150 | return result; 151 | } 152 | 153 | 154 | v2i world_pos_to_pixel_pos(v2f world_pos, float um_per_pixel, i32 level) { 155 | v2i result; 156 | i32 downsample_factor = 1 << level; 157 | result.x = (i32)roundf((world_pos.x / um_per_pixel) / (float)downsample_factor); 158 | result.y = (i32)roundf((world_pos.y / um_per_pixel) / (float)downsample_factor); 159 | return result; 160 | } 161 | 162 | 163 | i32 tile_pos_from_world_pos(float world_pos, float tile_side) { 164 | ASSERT(tile_side > 0); 165 | float tile_float = (world_pos / tile_side); 166 | float tile = (i32)floorf(tile_float); 167 | return tile; 168 | } 169 | 170 | bounds2i world_bounds_to_tile_bounds(bounds2f* world_bounds, float tile_width, float tile_height, v2f image_pos) { 171 | bounds2i result = {0}; 172 | result.left = tile_pos_from_world_pos(world_bounds->left - image_pos.x, tile_width); 173 | result.top = tile_pos_from_world_pos(world_bounds->top - image_pos.y, tile_height); 174 | result.right = tile_pos_from_world_pos(world_bounds->right - image_pos.x, tile_width) + 1; 175 | result.bottom = tile_pos_from_world_pos(world_bounds->bottom - image_pos.y, tile_height) + 1; 176 | return result; 177 | } 178 | 179 | bounds2f tile_bounds_to_world_bounds(bounds2i tile_bounds, float tile_width, float tile_height, v2f image_pos) { 180 | bounds2f result = {0}; 181 | result.left = tile_bounds.left * tile_width + image_pos.x; 182 | result.right = (tile_bounds.right) * tile_width + image_pos.x; 183 | result.top = tile_bounds.top * tile_height + image_pos.y; 184 | result.bottom = (tile_bounds.bottom) * tile_height + image_pos.y; 185 | return result; 186 | } 187 | 188 | bounds2f bounds_from_center_point(v2f center, float r_minus_l, float t_minus_b) { 189 | bounds2f bounds = { 190 | .left = center.x - r_minus_l * 0.5f, 191 | .top = center.y - t_minus_b * 0.5f, 192 | .right = center.x + r_minus_l * 0.5f, 193 | .bottom = center.y + t_minus_b * 0.5f, 194 | }; 195 | return bounds; 196 | } 197 | 198 | bounds2f bounds_from_pivot_point(v2f pivot, v2f pivot_relative_pos, float r_minus_l, float t_minus_b) { 199 | bounds2f bounds = { 200 | .left = pivot.x - r_minus_l * pivot_relative_pos.x, 201 | .top = pivot.y - t_minus_b * pivot_relative_pos.y, 202 | .right = pivot.x + r_minus_l * (1.0f - pivot_relative_pos.x), 203 | .bottom = pivot.y + t_minus_b * (1.0f - pivot_relative_pos.y), 204 | }; 205 | return bounds; 206 | } 207 | 208 | bounds2f bounds_from_points(v2f* points, i32 point_count) { 209 | bounds2f bounds = { FLT_MAX, FLT_MAX, FLT_MIN, FLT_MIN}; 210 | for (i32 i = 0; i < point_count; ++i) { 211 | v2f p = points[i]; 212 | bounds.min.x = MIN(bounds.min.x, p.x); 213 | bounds.min.y = MIN(bounds.min.y, p.y); 214 | bounds.max.x = MAX(bounds.max.x, p.x); 215 | bounds.max.y = MAX(bounds.max.y, p.y); 216 | } 217 | return bounds; 218 | } 219 | 220 | polygon4v2f rotated_rectangle(float width, float height, float rotation) { 221 | float sin_theta = sinf(rotation); 222 | float cos_theta = cosf(rotation); 223 | 224 | float right = 0.5f * width; 225 | float left = -right; 226 | float bottom = 0.5f * height; 227 | float top = -bottom; 228 | 229 | polygon4v2f result = { .values = { 230 | {left * cos_theta - top * sin_theta, top * cos_theta + left * sin_theta }, // top left 231 | {right * cos_theta - top * sin_theta, top * cos_theta + right * sin_theta }, // top right 232 | {left * cos_theta - bottom * sin_theta, bottom * cos_theta + left * sin_theta }, // bottom left 233 | {right * cos_theta - bottom * sin_theta, bottom * cos_theta + right * sin_theta }, // bottom right 234 | }}; 235 | return result; 236 | } 237 | 238 | bounds2i world_bounds_to_pixel_bounds(bounds2f* world_bounds, float mpp_x, float mpp_y) { 239 | bounds2i pixel_bounds = {0}; 240 | pixel_bounds.left = (i32) floorf(world_bounds->left / mpp_x); 241 | pixel_bounds.right = (i32) ceilf(world_bounds->right / mpp_x); 242 | pixel_bounds.top = (i32) floorf(world_bounds->top / mpp_y); 243 | pixel_bounds.bottom = (i32) ceilf(world_bounds->bottom / mpp_y); 244 | return pixel_bounds; 245 | } 246 | 247 | rect2f pixel_rect_to_world_rect(rect2i pixel_rect, float mpp_x, float mpp_y) { 248 | rect2f world_rect = {0}; 249 | world_rect.x = pixel_rect.x * mpp_x; 250 | world_rect.y = pixel_rect.y * mpp_y; 251 | world_rect.w = pixel_rect.w * mpp_x; 252 | world_rect.h = pixel_rect.h * mpp_y; 253 | return world_rect; 254 | } 255 | 256 | // https://math.stackexchange.com/questions/330269/the-distance-from-a-point-to-a-line-segment 257 | // https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment 258 | v2f project_point_on_line_segment(v2f point, v2f line_start, v2f line_end, float* t_ptr) { 259 | v2f line_end_minus_start = v2f_subtract(line_end, line_start); 260 | float segment_length_sq = v2f_length_squared(line_end_minus_start); 261 | if (segment_length_sq == 0.0f) { 262 | return line_start; // line_start == line_end case 263 | } 264 | // Consider the line extending the segment, parameterized as v + t (w - v). 265 | // We find projection of point p onto the line. 266 | // It falls where t = [(p-v) . (w-v)] / |w-v|^2 267 | // We clamp t from [0,1] to handle points outside the segment vw. 268 | float t = v2f_dot(v2f_subtract(point, line_start), line_end_minus_start) / segment_length_sq; 269 | float t_clamped = MAX(0, MIN(1, t)); 270 | if (t_ptr != NULL) { 271 | *t_ptr = t_clamped; // return value to caller 272 | } 273 | v2f projection = v2f_lerp(line_start, line_end_minus_start, t_clamped); // lerp 274 | return projection; 275 | } 276 | 277 | bool v2f_within_bounds(bounds2f bounds, v2f point) { 278 | bool result = point.x >= bounds.left && point.x < bounds.right && point.y >= bounds.top && point.y < bounds.bottom; 279 | return result; 280 | } 281 | 282 | bool v2f_between_points(v2f v, v2f p0, v2f p1) { 283 | bool result = v.x >= p0.x && v.x < p1.x && v.y >= p0.y && v.y < p1.y; 284 | return result; 285 | } 286 | 287 | v2f v2f_average(v2f a, v2f b) { 288 | v2f result = {a.x + b.x, a.y + b.y}; 289 | result.x *= 0.5f; 290 | result.y *= 0.5f; 291 | return result; 292 | } 293 | 294 | corner_enum get_closest_corner(v2f center_point, v2f p) { 295 | if (p.x <= center_point.x) { 296 | if (p.y <= center_point.y) { 297 | return CORNER_TOP_LEFT; 298 | } else { 299 | return CORNER_BOTTOM_LEFT; 300 | } 301 | } else { 302 | if (p.y <= center_point.y) { 303 | return CORNER_TOP_RIGHT; 304 | } else { 305 | return CORNER_BOTTOM_RIGHT; 306 | } 307 | } 308 | } 309 | 310 | v2f get_corner_pos(rect2f rect, corner_enum corner) { 311 | switch(corner) { 312 | default: 313 | case CORNER_TOP_LEFT: { 314 | return (v2f){rect.x, rect.y}; 315 | } break; 316 | case CORNER_TOP_RIGHT: { 317 | return (v2f){rect.x + rect.w, rect.y}; 318 | } break; 319 | case CORNER_BOTTOM_LEFT: { 320 | return (v2f){rect.x, rect.y + rect.h}; 321 | } break; 322 | case CORNER_BOTTOM_RIGHT: { 323 | return (v2f){rect.x + rect.w, rect.y + rect.h}; 324 | } break; 325 | } 326 | } 327 | 328 | 329 | 330 | 331 | -------------------------------------------------------------------------------- /src/platform/common.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2024, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | #ifndef COMMON_H 30 | #define COMMON_H 31 | 32 | #if __has_include("config.h") 33 | #include "config.h" 34 | #endif 35 | 36 | #ifndef APP_TITLE 37 | #define APP_TITLE "Application" 38 | #endif 39 | 40 | #ifndef _LARGEFILE64_SOURCE 41 | #define _LARGEFILE64_SOURCE 42 | #endif 43 | #ifndef _FILE_OFFSET_BITS 44 | #define _FILE_OFFSET_BITS 64 45 | #endif 46 | 47 | // Platform detection 48 | #ifdef _WIN32 49 | #define WINDOWS 1 50 | #ifndef _WIN32_WINNT 51 | #define _WIN32_WINNT 0x0600 52 | #endif 53 | #define WINVER 0x0600 54 | #define OPENGL_H 55 | #define PATH_SEP "\\" 56 | #else 57 | #define WINDOWS 0 58 | #define PATH_SEP "/" 59 | #endif 60 | 61 | #ifdef __APPLE__ 62 | #define APPLE 1 63 | #include 64 | #if TARGET_CPU_ARM64 65 | #define APPLE_ARM 1 66 | #else 67 | #define APPLE_ARM 0 68 | #endif 69 | #define OPENGL_H 70 | #else 71 | #define APPLE 0 72 | #define APPLE_ARM 0 73 | #endif 74 | 75 | #if LINUX 76 | #include 77 | #endif 78 | #if !WINDOWS 79 | #include // for access(), F_OK 80 | #include 81 | //#define _aligned_malloc(size, alignment) aligned_alloc(alignment, size) 82 | //#define _aligned_free(ptr) free(ptr) 83 | #if __SIZEOF_POINTER__==8 84 | #define fseeko64 fseek 85 | #define fopen64 fopen 86 | #define fgetpos64 fgetpos 87 | #define fsetpos64 fsetpos 88 | #endif 89 | #endif 90 | 91 | #if defined(__linux__) || (!defined(__APPLE__) && (defined(__unix__) || defined(_POSIX_VERSION))) 92 | #define LINUX 1 93 | #define OPENGL_H 94 | #else 95 | #define LINUX 0 96 | #endif 97 | 98 | // Compiler detection 99 | #ifdef _MSC_VER 100 | #define COMPILER_MSVC 1 101 | #else 102 | #define COMPILER_MSVC 0 103 | #endif 104 | 105 | #ifdef __GNUC__ 106 | #define COMPILER_GCC 1 107 | #else 108 | #define COMPILER_GCC 0 109 | #endif 110 | 111 | // IDE detection (for dealing with pesky preprocessor highlighting issues) 112 | #if defined(__JETBRAINS_IDE__) 113 | #define CODE_EDITOR 1 114 | #else 115 | #define CODE_EDITOR 0 116 | #endif 117 | 118 | // Use 64-bit file offsets for fopen, etc. 119 | #ifndef _FILE_OFFSET_BITS 120 | #define _FILE_OFFSET_BITS 64 121 | #endif 122 | 123 | #include 124 | #include 125 | #include 126 | #include 127 | #include 128 | #include 129 | #include 130 | #include 131 | 132 | #if COMPILER_MSVC 133 | #include 134 | #define access _access 135 | #define F_OK 0 // check for file existence 136 | #define S_ISDIR(m) (((m) & 0xF000) == 0x4000) // check for whether a file is a directory (from stat.h) 137 | #define strncasecmp _strnicmp 138 | #define strcasecmp _stricmp 139 | #define alloca _alloca 140 | #define fseeko64 _fseeki64 141 | #define fopen64 fopen 142 | #endif 143 | 144 | #ifndef THREAD_LOCAL 145 | #ifdef _MSC_VER 146 | #define THREAD_LOCAL __declspec(thread) 147 | #else 148 | #define THREAD_LOCAL __thread 149 | #endif 150 | #endif 151 | 152 | #if LINUX 153 | #include 154 | #endif 155 | 156 | // adapted from lz4.c 157 | #ifndef FORCE_INLINE 158 | # ifdef _MSC_VER /* Visual Studio */ 159 | # define FORCE_INLINE static __forceinline 160 | # else 161 | # if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ 162 | # ifdef __GNUC__ 163 | # define FORCE_INLINE static inline __attribute__((always_inline)) 164 | # else 165 | # define FORCE_INLINE static inline 166 | # endif 167 | # else 168 | # define FORCE_INLINE static 169 | # endif /* __STDC_VERSION__ */ 170 | # endif /* _MSC_VER */ 171 | #endif /* LZ4_FORCE_INLINE */ 172 | 173 | // Wrappers for using libc versions of malloc(), realloc() and free(), if you really need to. 174 | // You can use these if you replaced regular malloc with ltalloc (see below). 175 | FORCE_INLINE void* libc_malloc(size_t size) { 176 | return malloc(size); 177 | } 178 | FORCE_INLINE void* libc_realloc(void* memory, size_t new_size) { 179 | return realloc(memory, new_size); 180 | } 181 | FORCE_INLINE void libc_free(void* memory) { 182 | free(memory); 183 | } 184 | 185 | // ltalloc provides a faster malloc(), realloc(), free() 186 | // https://github.com/r-lyeh-archived/ltalloc 187 | // To replace regular malloc with ltalloc: #define USE_LTALLOC_INSTEAD_OF_MALLOC in config.h 188 | #if __has_include("ltalloc.h") 189 | #define IS_LTALLOC_AVAILABLE 1 190 | #include "ltalloc.h" 191 | #ifdef USE_LTALLOC_INSTEAD_OF_MALLOC 192 | #define malloc ltmalloc 193 | #define calloc ltcalloc 194 | #define free ltfree 195 | #define realloc ltrealloc 196 | #endif 197 | #else 198 | #define IS_LTALLOC_AVAILABLE 0 199 | #endif 200 | 201 | // Typesafe dynamic array and hash tables for C 202 | // https://github.com/nothings/stb/blob/master/stb_ds.h 203 | // NOTE: need to define STB_DS_IMPLEMENTATION in one source file 204 | #if __has_include("stb_ds.h") 205 | #if IS_LTALLOC_AVAILABLE 206 | #define STBDS_REALLOC(context,ptr,size) ltrealloc((ptr),(size)) 207 | #define STBDS_FREE(context,ptr) ltfree((ptr)) 208 | #endif 209 | #include "stb_ds.h" 210 | #define arrlastptr(a) ((a)+(stbds_header(a)->length-1)) 211 | #endif 212 | 213 | // NOTE: need to define STB_SPRINTF_IMPLEMENTATION in one source file 214 | #ifndef STB_SPRINTF_H_INCLUDE 215 | #include "stb_sprintf.h" 216 | #endif 217 | #undef sprintf 218 | #undef snprintf 219 | #undef vsprintf 220 | #undef vsnprintf 221 | #define sprintf stbsp_sprintf 222 | #define snprintf stbsp_snprintf 223 | #define vsprintf stbsp_vsprintf 224 | #define vsnprintf stbsp_vsnprintf 225 | 226 | // Catch prints to the console so that we can also log them, display them in a GUI, etc. 227 | #ifndef USE_CONSOLE_GUI 228 | #define USE_CONSOLE_GUI 0 229 | #endif 230 | 231 | #ifdef __cplusplus 232 | extern "C" { 233 | #endif 234 | #if !USE_CONSOLE_GUI 235 | #define console_print printf 236 | #define console_print_error(...) fprintf(stderr, __VA_ARGS__) 237 | extern bool is_verbose_mode; // NOTE: necessary to define this variable somewhere 238 | #define console_print_verbose(...) do { if (is_verbose_mode) fprintf(stdout, __VA_ARGS__); } while(0) 239 | #else 240 | void console_print(const char *fmt, ...); // defined in console.cpp 241 | void console_print_verbose(const char *fmt, ...); // defined in console.cpp 242 | void console_print_error(const char *fmt, ...); // // defined in console.cpp 243 | #endif 244 | #ifdef __cplusplus 245 | } 246 | #endif 247 | 248 | // Typedef choices for numerical types 249 | typedef int8_t i8; 250 | typedef int16_t i16; 251 | typedef int32_t i32; 252 | typedef long long i64; 253 | 254 | typedef uint8_t u8; 255 | typedef uint16_t u16; 256 | typedef uint32_t u32; 257 | typedef unsigned long long u64; 258 | 259 | typedef int32_t bool32; 260 | typedef int8_t bool8; 261 | 262 | // String type with a known size (don't assume zero-termination) 263 | typedef struct str_t { 264 | const char* s; 265 | size_t len; 266 | } str_t; 267 | 268 | // Convenience macros 269 | #define COUNT(array) (sizeof(array) / sizeof((array)[0])) 270 | #ifndef MIN 271 | #define MIN(a, b) (((a) < (b)) ? (a) : (b)) 272 | #endif 273 | #ifndef MAX 274 | #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 275 | #endif 276 | #define ATLEAST(a, b) MAX(a, b) 277 | #define ATMOST(a, b) MIN(a, b) 278 | #define ABS(x) ((x) < 0 ? -(x) : (x)) 279 | 280 | #define LERP(t,a,b) ( (a) + (t) * (float) ((b)-(a)) ) 281 | #define UNLERP(t,a,b) ( ((t) - (a)) / (float) ((b) - (a)) ) 282 | 283 | #define CLAMP(x,xmin,xmax) ((x) < (xmin) ? (xmin) : (x) > (xmax) ? (xmax) : (x)) 284 | 285 | #define MACRO_VAR(name) concat(name, __LINE__) 286 | #define defer(start, end) for (int MACRO_VAR(_i_) = (start, 0); !MACRO_VAR(_i_); (MACRO_VAR(_i_)+=1,end)) 287 | 288 | 289 | #define SQUARE(x) ((x)*(x)) 290 | 291 | #define memset_zero(x) memset((x), 0, sizeof(*x)) 292 | 293 | #define KILOBYTES(n) (1024LL*(n)) 294 | #define MEGABYTES(n) (1024LL*KILOBYTES(n)) 295 | #define GIGABYTES(n) (1024LL*MEGABYTES(n)) 296 | #define TERABYTES(n) (1024LL*GIGABYTES(n)) 297 | 298 | 299 | #if defined(SOURCE_PATH_SIZE) && !defined(__FILENAME__) 300 | #define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE) 301 | #elif !defined(__FILENAME__) 302 | #define __FILENAME__ __FILE__ 303 | #endif 304 | 305 | #define fatal_error(message) _fatal_error(__FILE__, __LINE__, __func__, "" message) 306 | #if FATAL_ERROR_DONT_INLINE 307 | #define FATAL_ERROR_INLINE_SPECIFIER 308 | // Not inlining _fatal_error() shaves a few kilobytes off the executable size. 309 | // NOTE: if FATAL_ERROR_DONT_INLINE is enabled, FATAL_ERROR_IMPLEMENTATION must be defined in exactly 1 source file. 310 | #ifdef __cplusplus 311 | extern "C" 312 | #endif 313 | void _fatal_error(const char* source_filename, i32 line, const char* func, const char* message); 314 | #else 315 | #define FATAL_ERROR_IMPLEMENTATION 316 | #define FATAL_ERROR_INLINE_SPECIFIER FORCE_INLINE 317 | #endif //FATAL_ERROR_DONT_INLINE 318 | #ifdef FATAL_ERROR_IMPLEMENTATION 319 | FATAL_ERROR_INLINE_SPECIFIER void _fatal_error(const char* source_filename, i32 line, const char* func, const char* message) { 320 | fprintf(stderr, "%s(): %s:%d\n", func, source_filename, line); 321 | if (message[0] != '\0') fprintf(stderr, "Error: %s\n", message); 322 | fprintf(stderr, "A fatal error occurred (aborting).\n"); 323 | #if DO_DEBUG 324 | #if COMPILER_GCC 325 | __builtin_trap(); 326 | #elif COMPILER_MSVC 327 | __debugbreak(); 328 | #endif 329 | #endif 330 | abort(); 331 | } 332 | #endif //FATAL_ERROR_IMPLEMENTATION 333 | 334 | #ifndef NDEBUG 335 | #define DO_DEBUG 1 336 | #define ASSERT(expr) do {if (!(expr)) fatal_error();} while(0) 337 | #else 338 | #define DO_DEBUG 0 339 | #define ASSERT(expr) 340 | #endif 341 | 342 | //http://www.pixelbeat.org/programming/gcc/static_assert.html 343 | #define ASSERT_CONCAT_(a, b) a##b 344 | #define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b) 345 | /* These can't be used after statements in c89. */ 346 | #ifdef __COUNTER__ 347 | #define STATIC_ASSERT(e) \ 348 | ;enum { ASSERT_CONCAT(ASSERT_CONCAT(static_assert_, __COUNTER__), __LINE__) = 1/(int)(!!(e)) } 349 | #else 350 | /* This can't be used twice on the same line so ensure if using in headers 351 | * that the headers are not included twice (by wrapping in #ifndef...#endif) 352 | * Note it doesn't cause an issue when used on same line of separate modules 353 | * compiled with gcc -combine -fwhole-program. */ 354 | #define STATIC_ASSERT(e) \ 355 | ;enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1/(int)(!!(e)) } 356 | #endif 357 | 358 | #define DUMMY_STATEMENT do { int __x = 5; } while(0) 359 | 360 | FORCE_INLINE u64 next_pow2(u64 x) { 361 | ASSERT(x > 1); 362 | x -= 1; 363 | x |= (x >> 1); 364 | x |= (x >> 2); 365 | x |= (x >> 4); 366 | x |= (x >> 8); 367 | x |= (x >> 16); 368 | x |= (x >> 32); 369 | return x + 1; 370 | } 371 | 372 | FORCE_INLINE i32 div_floor(i32 a, i32 b) { 373 | return a / b - (a % b < 0); 374 | } 375 | 376 | 377 | 378 | 379 | #endif 380 | -------------------------------------------------------------------------------- /src/isyntax/isyntax.h: -------------------------------------------------------------------------------- 1 | /* 2 | BSD 2-Clause License 3 | 4 | Copyright (c) 2019-2025, Pieter Valkema 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #ifdef __cplusplus 31 | extern "C" { 32 | #endif 33 | 34 | #include "common.h" 35 | #include "libisyntax.h" 36 | #include "block_allocator.h" 37 | #include "work_queue.h" 38 | 39 | #include "yxml.h" 40 | 41 | #define DWT_COEFF_BITS 16 42 | #if (DWT_COEFF_BITS==16) 43 | typedef i16 icoeff_t; 44 | #else 45 | typedef i32 icoeff_t; 46 | #endif 47 | 48 | #define ISYNTAX_IDWT_PAD_L 4 49 | #define ISYNTAX_IDWT_PAD_R 4 50 | #define ISYNTAX_IDWT_FIRST_VALID_PIXEL 7 51 | 52 | #define ISYNTAX_ADJ_TILE_TOP_LEFT 0x100 53 | #define ISYNTAX_ADJ_TILE_TOP_CENTER 0x80 54 | #define ISYNTAX_ADJ_TILE_TOP_RIGHT 0x40 55 | #define ISYNTAX_ADJ_TILE_CENTER_LEFT 0x20 56 | #define ISYNTAX_ADJ_TILE_CENTER 0x10 57 | #define ISYNTAX_ADJ_TILE_CENTER_RIGHT 8 58 | #define ISYNTAX_ADJ_TILE_BOTTOM_LEFT 4 59 | #define ISYNTAX_ADJ_TILE_BOTTOM_CENTER 2 60 | #define ISYNTAX_ADJ_TILE_BOTTOM_RIGHT 1 61 | 62 | 63 | enum isyntax_image_type_enum { 64 | ISYNTAX_IMAGE_TYPE_NONE = 0, 65 | ISYNTAX_IMAGE_TYPE_MACROIMAGE = 1, 66 | ISYNTAX_IMAGE_TYPE_LABELIMAGE = 2, 67 | ISYNTAX_IMAGE_TYPE_WSI = 3, 68 | }; 69 | 70 | enum isyntax_node_type_enum { 71 | ISYNTAX_NODE_NONE = 0, 72 | ISYNTAX_NODE_LEAF = 1, // ex. PHILIPS 73 | ISYNTAX_NODE_BRANCH = 2, // ex. (leaf nodes) 74 | ISYNTAX_NODE_ARRAY = 3, // (contains one or more similar type of leaf/branch nodes) 75 | }; 76 | 77 | // NOTE: Most of these have DICOM group 0x301D. Currently there seem to be no element ID collisions. 78 | enum isyntax_group_data_object_dicom_element_enum { 79 | // Group 0x301D 80 | PIM_DP_SCANNED_IMAGES = 0x1003, // DPScannedImage 81 | DP_IMAGE_POST_PROCESSING = 0x1014, // DPImagePostProcessing 82 | DP_WAVELET_QUANTIZER_SETTINGS_PER_COLOR = 0x1019, // DPWaveletQuantizerSeetingsPerColor 83 | DP_WAVELET_QUANTIZER_SETTINGS_PER_LEVEL = 0x101a, // DPWaveletQuantizerSeetingsPerLevel 84 | UFS_IMAGE_GENERAL_HEADERS = 0x2000, // UFSImageGeneralHeader 85 | UFS_IMAGE_DIMENSIONS = 0x2003, // UFSImageDimension 86 | UFS_IMAGE_BLOCK_HEADER_TEMPLATES = 0x2009, // UFSImageBlockHeaderTemplate 87 | UFS_IMAGE_DIMENSION_RANGES = 0x200a, // UFSImageDimensionRange 88 | DP_COLOR_MANAGEMENT = 0x200b, // DPColorManagement 89 | UFS_IMAGE_BLOCK_HEADERS = 0x200d, // UFSImageBlockHeader // new in iSyntax v2 90 | UFS_IMAGE_CLUSTER_HEADER_TEMPLATES = 0x2016, // UFSImageClusterHeaderTemplate // new in iSyntax v2 91 | UFS_IMAGE_VALID_DATA_ENVELOPES = 0x2023, // UFSImageValidDataEnvelope // new in iSyntax v2 92 | UFS_IMAGE_OPP_EXTREME_VERTICES = 0x2024, // UFSImageOppExtremeVertex // new in iSyntax v2 93 | // Group 8B01 94 | PIIM_PIXEL_DATA_REPRESENTATION_SEQUENCE = 0x1001, // PixelDataRepresentation 95 | }; 96 | 97 | enum isyntax_data_object_flag_enum { 98 | ISYNTAX_OBJECT_DPUfsImport = 1, 99 | ISYNTAX_OBJECT_DPScannedImage = 2, 100 | ISYNTAX_OBJECT_UFSImageGeneralHeader = 4, 101 | ISYNTAX_OBJECT_UFSImageBlockHeaderTemplate = 8, 102 | ISYNTAX_OBJECT_UFSImageDimension = 0x10, 103 | ISYNTAX_OBJECT_UFSImageDimensionRange = 0x20, 104 | ISYNTAX_OBJECT_DPColorManagement = 0x40, 105 | ISYNTAX_OBJECT_DPImagePostProcessing = 0x80, 106 | ISYNTAX_OBJECT_DPWaveletQuantizerSeetingsPerColor = 0x100, 107 | ISYNTAX_OBJECT_DPWaveletQuantizerSeetingsPerLevel = 0x200, 108 | ISYNTAX_OBJECT_PixelDataRepresentation = 0x400, 109 | ISYNTAX_OBJECT_UFSImageBlockHeader = 0x800, // new in iSyntax v2 110 | ISYNTAX_OBJECT_UFSImageClusterHeaderTemplate = 0x1000, // new in iSyntax v2 111 | ISYNTAX_OBJECT_UFSImageValidDataEnvelope = 0x2000, // new in iSyntax v2 112 | ISYNTAX_OBJECT_UFSImageOppExtremeVertex = 0x4000, // new in iSyntax v2 113 | }; 114 | 115 | 116 | #pragma pack(push, 1) 117 | typedef struct isyntax_dicom_tag_header_t { 118 | u16 group; 119 | u16 element; 120 | u32 size; 121 | } isyntax_dicom_tag_header_t; 122 | 123 | typedef struct isyntax_partial_block_header_t { 124 | isyntax_dicom_tag_header_t sequence_element_header; 125 | isyntax_dicom_tag_header_t block_coordinates_header; 126 | u32 x_coordinate; 127 | u32 y_coordinate; 128 | u32 color_component; 129 | u32 scale; 130 | u32 coefficient; 131 | /* [MISSING] dicom_tag_header_t block_data_offset_header; */ 132 | /* [MISSING] u64 block_data_offset; */ 133 | /* [MISSING] dicom_tag_header_t block_size_header; */ 134 | /* [MISSING] u64 block_size; */ 135 | isyntax_dicom_tag_header_t block_header_template_id_header; 136 | u32 block_header_template_id; 137 | } isyntax_partial_block_header_t; 138 | 139 | typedef struct isyntax_full_block_header_t { 140 | isyntax_dicom_tag_header_t sequence_element_header; 141 | isyntax_dicom_tag_header_t block_coordinates_header; 142 | u32 x_coordinate; 143 | u32 y_coordinate; 144 | u32 color_component; 145 | u32 scale; 146 | u32 coefficient; 147 | isyntax_dicom_tag_header_t block_data_offset_header; 148 | u64 block_data_offset; 149 | isyntax_dicom_tag_header_t block_size_header; 150 | u64 block_size; 151 | isyntax_dicom_tag_header_t block_header_template_id_header; 152 | u32 block_header_template_id; 153 | } isyntax_full_block_header_t; 154 | 155 | typedef struct isyntax_seektable_codeblock_header_t { 156 | isyntax_dicom_tag_header_t start_header; 157 | isyntax_dicom_tag_header_t block_data_offset_header; 158 | u64 block_data_offset; 159 | isyntax_dicom_tag_header_t block_size_header; 160 | u64 block_size; 161 | } isyntax_seektable_codeblock_header_t; 162 | #pragma pack(pop) 163 | 164 | typedef struct isyntax_image_dimension_range_t { 165 | i32 start; 166 | i32 step; 167 | i32 end; 168 | i32 numsteps; 169 | } isyntax_image_dimension_range_t; 170 | 171 | typedef struct isyntax_block_header_template_t { 172 | u32 block_width; // e.g. 128 173 | u32 block_height; // e.g. 128 174 | u8 color_component; // 0=Y 1=Co 2=Cg 175 | u8 scale; // range 0-8 176 | u8 waveletcoeff; // either 1 for LL, or 3 for LH+HL+HH 177 | } isyntax_block_header_template_t; 178 | 179 | typedef struct isyntax_cluster_block_header_t { 180 | u32 x_coordinate; 181 | u32 y_coordinate; 182 | u32 color_component; 183 | u32 scale; 184 | u32 coefficient; 185 | } isyntax_cluster_block_header_t; 186 | 187 | typedef struct isyntax_cluster_relative_coords_t { 188 | u32 raw_coords[5]; 189 | u32 block_header_template_id; 190 | u32 x; 191 | u32 y; 192 | u32 color_component; 193 | u32 scale; 194 | u32 waveletcoeff; 195 | } isyntax_cluster_relative_coords_t; 196 | 197 | #define MAX_CODEBLOCKS_PER_CLUSTER 70 // NOTE: what is the actual maximum possible? 198 | 199 | typedef struct isyntax_cluster_header_template_t { 200 | u32 base_x; 201 | u32 base_y; 202 | u8 base_scale; 203 | u8 base_waveletcoeff; 204 | u8 base_color_component; 205 | isyntax_cluster_relative_coords_t relative_coords_for_codeblock_in_cluster[MAX_CODEBLOCKS_PER_CLUSTER]; 206 | i32 codeblock_in_cluster_count; 207 | i32 dimension_order[5]; 208 | u8 dimension_count; 209 | } isyntax_cluster_header_template_t; 210 | 211 | typedef struct isyntax_valid_data_envelope_t { 212 | v2i vertices[64]; 213 | i32 vertex_count; 214 | } isyntax_valid_data_envelope_t; 215 | 216 | typedef struct isyntax_codeblock_t { 217 | u32 x_coordinate; 218 | u32 y_coordinate; 219 | u32 color_component; 220 | u32 scale; 221 | u32 coefficient; 222 | u64 block_data_offset; 223 | u64 block_size; 224 | u32 block_header_template_id; 225 | i32 x_adjusted; 226 | i32 y_adjusted; 227 | i32 block_x; 228 | i32 block_y; 229 | u64 block_id; 230 | } isyntax_codeblock_t; 231 | 232 | typedef struct isyntax_data_chunk_t { 233 | i64 offset; 234 | u32 size; 235 | i32 top_codeblock_index; 236 | i32 codeblock_count_per_color; 237 | i32 scale; 238 | i32 level_count; 239 | u8* data; 240 | } isyntax_data_chunk_t; 241 | 242 | typedef struct isyntax_tile_channel_t { 243 | icoeff_t* coeff_h; 244 | icoeff_t* coeff_ll; 245 | u32 neighbors_loaded; 246 | } isyntax_tile_channel_t; 247 | 248 | typedef struct isyntax_tile_t { 249 | u32 codeblock_index; 250 | u32 codeblock_chunk_index; 251 | u32 data_chunk_index; 252 | isyntax_tile_channel_t color_channels[3]; 253 | u32 ll_invalid_edges; 254 | bool exists; 255 | bool has_ll; 256 | bool has_h; 257 | bool is_submitted_for_h_coeff_decompression; 258 | bool is_submitted_for_loading; 259 | bool is_loaded; 260 | 261 | // Cache management. 262 | // TODO(avirodov): need to rethink this, maybe an external struct that points to isyntax_tile_t. The benefit 263 | // is that the cache is usually smaller than the number of tiles. The con is that I'll need to manage list memory 264 | // (probably another allocator for small objects - list nodes). 265 | bool cache_marked; 266 | struct isyntax_tile_t* cache_next; 267 | struct isyntax_tile_t* cache_prev; 268 | 269 | // Note(avirodov): this is needed for isyntax_reader. It is very convenient to be able to compute neighbors 270 | // from the tile itself, although at the cost of additional memory (3 ints) per tile. 271 | // TODO(avirodov): reconsider this as part of moving out cache_* fields, if applicable. 272 | // TODO(avirodov): tile_x and tile_y can be computed by O(1) pointer arithmetic given scale. Scale can be 273 | // computed as well, but in O(L) where L is number of levels, and that will be computed often. 274 | int tile_scale; 275 | int tile_x; 276 | int tile_y; 277 | } isyntax_tile_t; 278 | 279 | typedef struct isyntax_level_t { 280 | i32 scale; 281 | i32 width_in_tiles; 282 | i32 height_in_tiles; 283 | i32 width; 284 | i32 height; 285 | float downsample_factor; 286 | float um_per_pixel_x; 287 | float um_per_pixel_y; 288 | float x_tile_side_in_um; 289 | float y_tile_side_in_um; 290 | u64 tile_count; 291 | i32 origin_offset_in_pixels; 292 | v2f origin_offset; 293 | isyntax_tile_t* tiles; 294 | bool is_fully_loaded; 295 | } isyntax_level_t; 296 | 297 | typedef struct isyntax_image_t { 298 | u32 image_type; 299 | i64 base64_encoded_jpg_file_offset; 300 | size_t base64_encoded_jpg_len; 301 | i32 width_including_padding; 302 | i32 height_including_padding; 303 | i32 width; 304 | i32 height; 305 | i32 level0_padding; 306 | i32 offset_x; 307 | i32 offset_y; 308 | i32 level_count; 309 | i32 max_scale; 310 | isyntax_level_t levels[16]; 311 | i32 compressor_version; 312 | bool compression_is_lossy; 313 | i32 lossy_image_compression_ratio; 314 | i32 number_of_blocks; 315 | i32 codeblock_count; 316 | isyntax_codeblock_t* codeblocks; 317 | i32 data_chunk_count; 318 | isyntax_data_chunk_t* data_chunks; 319 | bool header_codeblocks_are_partial; 320 | bool first_load_complete; 321 | bool first_load_in_progress; 322 | i64 base64_encoded_icc_profile_file_offset; 323 | size_t base64_encoded_icc_profile_len; 324 | } isyntax_image_t; 325 | 326 | typedef struct isyntax_parser_node_t { 327 | u32 node_type; // leaf, branch, or array 328 | bool has_children; 329 | bool has_base64_content; 330 | u16 group; 331 | u16 element; 332 | } isyntax_parser_node_t; 333 | 334 | #define ISYNTAX_MAX_NODE_DEPTH 16 335 | 336 | typedef struct isyntax_xml_parser_t { 337 | yxml_t* x; 338 | isyntax_image_t* current_image; 339 | i32 running_image_index; 340 | u32 current_image_type; 341 | char* attrbuf; 342 | char* attrbuf_end; 343 | char* attrcur; 344 | size_t attrlen; 345 | size_t attrbuf_capacity; 346 | char* contentbuf; 347 | char* contentcur; 348 | size_t contentlen; 349 | size_t contentbuf_capacity; 350 | i64 content_file_offset; 351 | char current_dicom_attribute_name[256]; 352 | u32 current_dicom_group_tag; 353 | u32 current_dicom_element_tag; 354 | i32 attribute_index; 355 | u32 current_node_type; 356 | bool current_node_has_children; 357 | isyntax_parser_node_t node_stack[ISYNTAX_MAX_NODE_DEPTH]; 358 | i32 node_stack_index; 359 | isyntax_parser_node_t data_object_stack[ISYNTAX_MAX_NODE_DEPTH]; 360 | i32 data_object_stack_index; 361 | u32 data_object_flags; 362 | i32 block_header_template_index; 363 | i32 cluster_header_template_index; 364 | i32 block_header_index_for_cluster; 365 | i32 dimension_index; 366 | i32 valid_data_envelope_index; 367 | bool initialized; 368 | } isyntax_xml_parser_t; 369 | 370 | typedef struct isyntax_t { 371 | enum libisyntax_open_flags_t open_flags; 372 | i64 filesize; 373 | file_handle_t file_handle; 374 | isyntax_image_t images[16]; 375 | i32 image_count; 376 | isyntax_block_header_template_t block_header_templates[64]; 377 | i32 block_header_template_count; 378 | isyntax_cluster_header_template_t cluster_header_templates[8]; 379 | i32 cluster_header_template_count; 380 | isyntax_valid_data_envelope_t valid_data_envelopes[16]; 381 | i32 valid_data_envelope_count; 382 | i32 macro_image_index; 383 | i32 label_image_index; 384 | i32 wsi_image_index; 385 | isyntax_xml_parser_t parser; 386 | float mpp_x; 387 | float mpp_y; 388 | bool is_mpp_known; 389 | i32 block_width; 390 | i32 block_height; 391 | i32 tile_width; 392 | i32 tile_height; 393 | icoeff_t* black_dummy_coeff; 394 | icoeff_t* white_dummy_coeff; 395 | block_allocator_t* ll_coeff_block_allocator; 396 | block_allocator_t* h_coeff_block_allocator; 397 | bool is_block_allocator_owned; 398 | float loading_time; 399 | float total_rgb_transform_time; 400 | i32 data_model_major_version; // <100 (usually 5) for iSyntax format v1, >= 100 for iSyntax format v2 401 | char barcode[64]; 402 | bool is_barcode_read; 403 | isyntax_cache_t* cache; 404 | work_queue_t* work_submission_queue; 405 | volatile i32 refcount; 406 | } isyntax_t; 407 | 408 | // function prototypes 409 | bool isyntax_hulsken_decompress(u8 *compressed, size_t compressed_size, i32 block_width, i32 block_height, i32 coefficient, i32 compressor_version, i16* out_buffer); 410 | void isyntax_set_work_queue(isyntax_t* isyntax, work_queue_t* work_queue); 411 | bool isyntax_open(isyntax_t* isyntax, const char* filename, enum libisyntax_open_flags_t flags); 412 | void isyntax_destroy(isyntax_t* isyntax); 413 | void isyntax_idwt(icoeff_t* idwt, i32 quadrant_width, i32 quadrant_height, bool output_steps_as_png, const char* png_name); 414 | void isyntax_load_tile(isyntax_t* isyntax, isyntax_image_t* wsi, i32 scale, i32 tile_x, i32 tile_y, block_allocator_t* ll_coeff_block_allocator, 415 | u32* out_buffer_or_null, enum isyntax_pixel_format_t pixel_format); 416 | u32 isyntax_get_adjacent_tiles_mask(isyntax_level_t* level, i32 tile_x, i32 tile_y); 417 | u32 isyntax_get_adjacent_tiles_mask_only_existing(isyntax_level_t* level, i32 tile_x, i32 tile_y); 418 | u32 isyntax_idwt_tile_for_color_channel(isyntax_t* isyntax, isyntax_image_t* wsi, i32 scale, i32 tile_x, i32 tile_y, i32 color, icoeff_t* dest_buffer); 419 | void isyntax_decompress_codeblock_in_chunk(isyntax_codeblock_t* codeblock, i32 block_width, i32 block_height, u8* chunk, u64 chunk_base_offset, i32 compressor_version, i16* out_buffer); 420 | i32 isyntax_get_chunk_codeblocks_per_color_for_level(i32 level, bool has_ll); 421 | u8* isyntax_get_associated_image_pixels(isyntax_t* isyntax, isyntax_image_t* image, enum isyntax_pixel_format_t pixel_format); 422 | u8* isyntax_get_associated_image_jpeg(isyntax_t* isyntax, isyntax_image_t* image, u32* jpeg_size); 423 | u8* isyntax_get_icc_profile(isyntax_t* isyntax, isyntax_image_t* image, u32* icc_profile_size); 424 | 425 | 426 | #ifdef __cplusplus 427 | } 428 | #endif 429 | -------------------------------------------------------------------------------- /src/isyntax/isyntax_dwt.c: -------------------------------------------------------------------------------- 1 | // Code from the openjp2 library: 2 | // inverse discrete wavelet transform (5/3) 3 | 4 | // See: https://github.com/uclouvain/openjpeg 5 | // The OpenJPEG license information is included below: 6 | /* 7 | * The copyright in this software is being made available under the 2-clauses 8 | * BSD License, included below. This software may be subject to other third 9 | * party and contributor rights, including patent rights, and no such rights 10 | * are granted under this license. 11 | * 12 | * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium 13 | * Copyright (c) 2002-2014, Professor Benoit Macq 14 | * Copyright (c) 2001-2003, David Janssens 15 | * Copyright (c) 2002-2003, Yannick Verschueren 16 | * Copyright (c) 2003-2007, Francois-Olivier Devaux 17 | * Copyright (c) 2003-2014, Antonin Descampe 18 | * Copyright (c) 2005, Herve Drolon, FreeImage Team 19 | * Copyright (c) 2007, Jonathan Ballard 20 | * Copyright (c) 2007, Callum Lerwick 21 | * Copyright (c) 2017, IntoPIX SA 22 | * Copyright (c) 2021, Pieter Valkema 23 | * All rights reserved. 24 | * 25 | * Redistribution and use in source and binary forms, with or without 26 | * modification, are permitted provided that the following conditions 27 | * are met: 28 | * 1. Redistributions of source code must retain the above copyright 29 | * notice, this list of conditions and the following disclaimer. 30 | * 2. Redistributions in binary form must reproduce the above copyright 31 | * notice, this list of conditions and the following disclaimer in the 32 | * documentation and/or other materials provided with the distribution. 33 | * 34 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' 35 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 36 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 37 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 38 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 39 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 40 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 41 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 42 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 43 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 44 | * POSSIBILITY OF SUCH DAMAGE. 45 | */ 46 | // End of OpenJPEG copyright notice. 47 | 48 | #if (DWT_COEFF_BITS==16) 49 | #ifdef __AVX2__ 50 | /** Number of int32 values in a AVX2 register */ 51 | #define VREG_INT_COUNT 16 52 | #else 53 | /** Number of int32 values in a SSE2 register */ 54 | #define VREG_INT_COUNT 8 55 | #endif 56 | #else 57 | #ifdef __AVX2__ 58 | /** Number of int32 values in a AVX2 register */ 59 | #define VREG_INT_COUNT 8 60 | #else 61 | /** Number of int32 values in a SSE2 register */ 62 | #define VREG_INT_COUNT 4 63 | #endif 64 | #endif 65 | 66 | /** Number of columns that we can process in parallel in the vertical pass */ 67 | #define PARALLEL_COLS_53 (2*VREG_INT_COUNT) 68 | 69 | typedef struct dwt_local { 70 | icoeff_t* mem; 71 | i32 dn; /* number of elements in high pass band */ 72 | i32 sn; /* number of elements in low pass band */ 73 | i32 cas; /* 0 = start on even coord, 1 = start on odd coord */ 74 | } opj_dwt_t; 75 | 76 | static void opj_idwt53_h_cas0(icoeff_t* tmp, const i32 sn, const i32 len, icoeff_t* tiledp) { 77 | i32 i, j; 78 | const icoeff_t* in_even = &tiledp[0]; 79 | const icoeff_t* in_odd = &tiledp[sn]; 80 | 81 | icoeff_t d1c, d1n, s1n, s0c, s0n; 82 | 83 | ASSERT(len > 1); 84 | 85 | /* Improved version of the TWO_PASS_VERSION: */ 86 | /* Performs lifting in one single iteration. Saves memory */ 87 | /* accesses and explicit interleaving. */ 88 | s1n = in_even[0]; 89 | d1n = in_odd[0]; 90 | s0n = s1n - ((d1n + 1) >> 1); 91 | 92 | for (i = 0, j = 1; i < (len - 3); i += 2, j++) { 93 | d1c = d1n; 94 | s0c = s0n; 95 | 96 | s1n = in_even[j]; 97 | d1n = in_odd[j]; 98 | 99 | s0n = s1n - ((d1c + d1n + 2) >> 2); 100 | 101 | tmp[i ] = s0c; 102 | tmp[i + 1] = d1c + ((s0c + s0n) >> 1); 103 | } 104 | 105 | tmp[i] = s0n; 106 | 107 | if (len & 1) { 108 | tmp[len - 1] = in_even[(len - 1) / 2] - ((d1n + 1) >> 1); 109 | tmp[len - 2] = d1n + ((s0n + tmp[len - 1]) >> 1); 110 | } else { 111 | tmp[len - 1] = d1n + s0n; 112 | } 113 | memcpy(tiledp, tmp, (u32)len * sizeof(icoeff_t)); 114 | } 115 | 116 | static void opj_idwt53_h_cas1(icoeff_t* tmp, const i32 sn, const i32 len, icoeff_t* tiledp) { 117 | i32 i, j; 118 | const icoeff_t* in_even = &tiledp[sn]; 119 | const icoeff_t* in_odd = &tiledp[0]; 120 | 121 | icoeff_t s1, s2, dc, dn; 122 | 123 | ASSERT(len > 2); 124 | 125 | /* Improved version of the TWO_PASS_VERSION: */ 126 | /* Performs lifting in one single iteration. Saves memory */ 127 | /* accesses and explicit interleaving. */ 128 | 129 | s1 = in_even[1]; 130 | dc = in_odd[0] - ((in_even[0] + s1 + 2) >> 2); 131 | tmp[0] = in_even[0] + dc; 132 | 133 | for (i = 1, j = 1; i < (len - 2 - !(len & 1)); i += 2, j++) { 134 | 135 | s2 = in_even[j + 1]; 136 | 137 | dn = in_odd[j] - ((s1 + s2 + 2) >> 2); 138 | tmp[i ] = dc; 139 | tmp[i + 1] = s1 + ((dn + dc) >> 1); 140 | 141 | dc = dn; 142 | s1 = s2; 143 | } 144 | 145 | tmp[i] = dc; 146 | 147 | if (!(len & 1)) { 148 | dn = in_odd[len / 2 - 1] - ((s1 + 1) >> 1); 149 | tmp[len - 2] = s1 + ((dn + dc) >> 1); 150 | tmp[len - 1] = dn; 151 | } else { 152 | tmp[len - 1] = s1 + dc; 153 | } 154 | memcpy(tiledp, tmp, (u32)len * sizeof(icoeff_t)); 155 | } 156 | 157 | /* */ 158 | /* Inverse 5-3 wavelet transform in 1-D for one row. */ 159 | /* */ 160 | /* Performs interleave, inverse wavelet transform and copy back to buffer */ 161 | static void opj_idwt53_h(const opj_dwt_t *dwt, icoeff_t* tiledp) { 162 | const i32 sn = dwt->sn; 163 | const i32 len = sn + dwt->dn; 164 | if (dwt->cas == 0) { /* Left-most sample is on even coordinate */ 165 | if (len > 1) { 166 | opj_idwt53_h_cas0(dwt->mem, sn, len, tiledp); 167 | } else { 168 | /* Unmodified value */ 169 | } 170 | } else { /* Left-most sample is on odd coordinate */ 171 | if (len == 1) { 172 | tiledp[0] /= 2; 173 | } else if (len == 2) { 174 | icoeff_t* out = dwt->mem; 175 | const icoeff_t* in_even = &tiledp[sn]; 176 | const icoeff_t* in_odd = &tiledp[0]; 177 | out[1] = in_odd[0] - ((in_even[0] + 1) >> 1); 178 | out[0] = in_even[0] + out[1]; 179 | memcpy(tiledp, dwt->mem, (u32)len * sizeof(icoeff_t)); 180 | } else if (len > 2) { 181 | opj_idwt53_h_cas1(dwt->mem, sn, len, tiledp); 182 | } 183 | } 184 | } 185 | 186 | #if (defined(__SSE2__) || defined(__AVX2__)) 187 | 188 | /* Conveniency macros to improve the readabilty of the formulas */ 189 | #if __AVX2__ 190 | #define VREG __m256i 191 | #if (DWT_COEFF_BITS==16) 192 | #define LOAD_CST(x) _mm256_set1_epi16(x) 193 | #define ADD(x,y) _mm256_add_epi16((x),(y)) 194 | #define SUB(x,y) _mm256_sub_epi16((x),(y)) 195 | #define SAR(x,y) _mm256_srai_epi16((x),(y)) 196 | #else 197 | #define LOAD_CST(x) _mm256_set1_epi32(x) 198 | #define ADD(x,y) _mm256_add_epi32((x),(y)) 199 | #define SUB(x,y) _mm256_sub_epi32((x),(y)) 200 | #define SAR(x,y) _mm256_srai_epi32((x),(y)) 201 | #endif 202 | #define LOAD(x) _mm256_load_si256((const VREG*)(x)) 203 | #define LOADU(x) _mm256_loadu_si256((const VREG*)(x)) 204 | #define STORE(x,y) _mm256_store_si256((VREG*)(x),(y)) 205 | #define STOREU(x,y) _mm256_storeu_si256((VREG*)(x),(y)) 206 | #else 207 | #define VREG __m128i 208 | #if (DWT_COEFF_BITS==16) 209 | #define LOAD_CST(x) _mm_set1_epi16(x) 210 | #define ADD(x,y) _mm_add_epi16((x),(y)) 211 | #define SUB(x,y) _mm_sub_epi16((x),(y)) 212 | #define SAR(x,y) _mm_srai_epi16((x),(y)) 213 | #else 214 | #define LOAD_CST(x) _mm_set1_epi32(x) 215 | #define ADD(x,y) _mm_add_epi32((x),(y)) 216 | #define SUB(x,y) _mm_sub_epi32((x),(y)) 217 | #define SAR(x,y) _mm_srai_epi32((x),(y)) 218 | #endif 219 | #define LOAD(x) _mm_load_si128((const VREG*)(x)) 220 | #define LOADU(x) _mm_loadu_si128((const VREG*)(x)) 221 | #define STORE(x,y) _mm_store_si128((VREG*)(x),(y)) 222 | #define STOREU(x,y) _mm_storeu_si128((VREG*)(x),(y)) 223 | #endif 224 | #define ADD3(x,y,z) ADD(ADD(x,y),z) 225 | 226 | static void opj_idwt53_v_final_memcpy(icoeff_t* tiledp_col, const icoeff_t* tmp, i32 len, size_t stride) { 227 | for (i32 i = 0; i < len; ++i) { 228 | /* A memcpy(&tiledp_col[i * stride + 0], 229 | &tmp[PARALLEL_COLS_53 * i + 0], 230 | PARALLEL_COLS_53 * sizeof(i32)) 231 | would do but would be a tiny bit slower. 232 | We can take here advantage of our knowledge of alignment */ 233 | STOREU(&tiledp_col[(size_t)i * stride + 0], 234 | LOAD(&tmp[PARALLEL_COLS_53 * i + 0])); 235 | STOREU(&tiledp_col[(size_t)i * stride + VREG_INT_COUNT], 236 | LOAD(&tmp[PARALLEL_COLS_53 * i + VREG_INT_COUNT])); 237 | } 238 | } 239 | 240 | /** Vertical inverse 5x3 wavelet transform for 8 columns in SSE2, or 241 | * 16 in AVX2, when top-most pixel is on even coordinate */ 242 | static void opj_idwt53_v_cas0_mcols_SSE2_OR_AVX2(icoeff_t* tmp, const i32 sn, const i32 len, icoeff_t* tiledp_col, const size_t stride) { 243 | const icoeff_t* in_even = &tiledp_col[0]; 244 | const icoeff_t* in_odd = &tiledp_col[(size_t)sn * stride]; 245 | 246 | i32 i; 247 | size_t j; 248 | VREG d1c_0, d1n_0, s1n_0, s0c_0, s0n_0; 249 | VREG d1c_1, d1n_1, s1n_1, s0c_1, s0n_1; 250 | const VREG two = LOAD_CST(2); 251 | 252 | ASSERT(len > 1); 253 | /*#if __AVX2__ 254 | ASSERT(PARALLEL_COLS_53 == 16); 255 | ASSERT(VREG_INT_COUNT == 8); 256 | #else 257 | ASSERT(PARALLEL_COLS_53 == 8); 258 | ASSERT(VREG_INT_COUNT == 4); 259 | #endif*/ 260 | 261 | /* Note: loads of input even/odd values must be done in a unaligned */ 262 | /* fashion. But stores in tmp can be done with aligned store, since */ 263 | /* the temporary buffer is properly aligned */ 264 | ASSERT((size_t)tmp % (sizeof(icoeff_t) * VREG_INT_COUNT) == 0); 265 | 266 | s1n_0 = LOADU(in_even + 0); 267 | s1n_1 = LOADU(in_even + VREG_INT_COUNT); 268 | d1n_0 = LOADU(in_odd); 269 | d1n_1 = LOADU(in_odd + VREG_INT_COUNT); 270 | 271 | /* s0n = s1n - ((d1n + 1) >> 1); <==> */ 272 | /* s0n = s1n - ((d1n + d1n + 2) >> 2); */ 273 | s0n_0 = SUB(s1n_0, SAR(ADD3(d1n_0, d1n_0, two), 2)); 274 | s0n_1 = SUB(s1n_1, SAR(ADD3(d1n_1, d1n_1, two), 2)); 275 | 276 | for (i = 0, j = 1; i < (len - 3); i += 2, j++) { 277 | d1c_0 = d1n_0; 278 | s0c_0 = s0n_0; 279 | d1c_1 = d1n_1; 280 | s0c_1 = s0n_1; 281 | 282 | s1n_0 = LOADU(in_even + j * stride); 283 | s1n_1 = LOADU(in_even + j * stride + VREG_INT_COUNT); 284 | d1n_0 = LOADU(in_odd + j * stride); 285 | d1n_1 = LOADU(in_odd + j * stride + VREG_INT_COUNT); 286 | 287 | /*s0n = s1n - ((d1c + d1n + 2) >> 2);*/ 288 | s0n_0 = SUB(s1n_0, SAR(ADD3(d1c_0, d1n_0, two), 2)); 289 | s0n_1 = SUB(s1n_1, SAR(ADD3(d1c_1, d1n_1, two), 2)); 290 | 291 | STORE(tmp + PARALLEL_COLS_53 * (i + 0), s0c_0); 292 | STORE(tmp + PARALLEL_COLS_53 * (i + 0) + VREG_INT_COUNT, s0c_1); 293 | 294 | /* d1c + ((s0c + s0n) >> 1) */ 295 | STORE(tmp + PARALLEL_COLS_53 * (i + 1) + 0, 296 | ADD(d1c_0, SAR(ADD(s0c_0, s0n_0), 1))); 297 | STORE(tmp + PARALLEL_COLS_53 * (i + 1) + VREG_INT_COUNT, 298 | ADD(d1c_1, SAR(ADD(s0c_1, s0n_1), 1))); 299 | } 300 | 301 | STORE(tmp + PARALLEL_COLS_53 * (i + 0) + 0, s0n_0); 302 | STORE(tmp + PARALLEL_COLS_53 * (i + 0) + VREG_INT_COUNT, s0n_1); 303 | 304 | if (len & 1) { 305 | VREG tmp_len_minus_1; 306 | s1n_0 = LOADU(in_even + (size_t)((len - 1) / 2) * stride); 307 | /* tmp_len_minus_1 = s1n - ((d1n + 1) >> 1); */ 308 | tmp_len_minus_1 = SUB(s1n_0, SAR(ADD3(d1n_0, d1n_0, two), 2)); 309 | STORE(tmp + PARALLEL_COLS_53 * (len - 1), tmp_len_minus_1); 310 | /* d1n + ((s0n + tmp_len_minus_1) >> 1) */ 311 | STORE(tmp + PARALLEL_COLS_53 * (len - 2), 312 | ADD(d1n_0, SAR(ADD(s0n_0, tmp_len_minus_1), 1))); 313 | 314 | s1n_1 = LOADU(in_even + (size_t)((len - 1) / 2) * stride + VREG_INT_COUNT); 315 | /* tmp_len_minus_1 = s1n - ((d1n + 1) >> 1); */ 316 | tmp_len_minus_1 = SUB(s1n_1, SAR(ADD3(d1n_1, d1n_1, two), 2)); 317 | STORE(tmp + PARALLEL_COLS_53 * (len - 1) + VREG_INT_COUNT, 318 | tmp_len_minus_1); 319 | /* d1n + ((s0n + tmp_len_minus_1) >> 1) */ 320 | STORE(tmp + PARALLEL_COLS_53 * (len - 2) + VREG_INT_COUNT, 321 | ADD(d1n_1, SAR(ADD(s0n_1, tmp_len_minus_1), 1))); 322 | 323 | } else { 324 | STORE(tmp + PARALLEL_COLS_53 * (len - 1) + 0, 325 | ADD(d1n_0, s0n_0)); 326 | STORE(tmp + PARALLEL_COLS_53 * (len - 1) + VREG_INT_COUNT, 327 | ADD(d1n_1, s0n_1)); 328 | } 329 | 330 | opj_idwt53_v_final_memcpy(tiledp_col, tmp, len, stride); 331 | } 332 | 333 | 334 | /** Vertical inverse 5x3 wavelet transform for 8 columns in SSE2, or 335 | * 16 in AVX2, when top-most pixel is on odd coordinate */ 336 | static void opj_idwt53_v_cas1_mcols_SSE2_OR_AVX2(icoeff_t* tmp, const i32 sn, const i32 len, icoeff_t* tiledp_col, const size_t stride) { 337 | i32 i; 338 | size_t j; 339 | 340 | VREG s1_0, s2_0, dc_0, dn_0; 341 | VREG s1_1, s2_1, dc_1, dn_1; 342 | const VREG two = LOAD_CST(2); 343 | 344 | const icoeff_t* in_even = &tiledp_col[(size_t)sn * stride]; 345 | const icoeff_t* in_odd = &tiledp_col[0]; 346 | 347 | ASSERT(len > 2); 348 | /*#if __AVX2__ 349 | ASSERT(PARALLEL_COLS_53 == 16); 350 | ASSERT(VREG_INT_COUNT == 8); 351 | #else 352 | ASSERT(PARALLEL_COLS_53 == 8); 353 | ASSERT(VREG_INT_COUNT == 4); 354 | #endif*/ 355 | 356 | /* Note: loads of input even/odd values must be done in a unaligned */ 357 | /* fashion. But stores in tmp can be done with aligned store, since */ 358 | /* the temporary buffer is properly aligned */ 359 | ASSERT((size_t)tmp % (sizeof(icoeff_t) * VREG_INT_COUNT) == 0); 360 | 361 | s1_0 = LOADU(in_even + stride); 362 | /* in_odd[0] - ((in_even[0] + s1 + 2) >> 2); */ 363 | dc_0 = SUB(LOADU(in_odd + 0), 364 | SAR(ADD3(LOADU(in_even + 0), s1_0, two), 2)); 365 | STORE(tmp + PARALLEL_COLS_53 * 0, ADD(LOADU(in_even + 0), dc_0)); 366 | 367 | s1_1 = LOADU(in_even + stride + VREG_INT_COUNT); 368 | /* in_odd[0] - ((in_even[0] + s1 + 2) >> 2); */ 369 | dc_1 = SUB(LOADU(in_odd + VREG_INT_COUNT), 370 | SAR(ADD3(LOADU(in_even + VREG_INT_COUNT), s1_1, two), 2)); 371 | STORE(tmp + PARALLEL_COLS_53 * 0 + VREG_INT_COUNT, 372 | ADD(LOADU(in_even + VREG_INT_COUNT), dc_1)); 373 | 374 | for (i = 1, j = 1; i < (len - 2 - !(len & 1)); i += 2, j++) { 375 | 376 | s2_0 = LOADU(in_even + (j + 1) * stride); 377 | s2_1 = LOADU(in_even + (j + 1) * stride + VREG_INT_COUNT); 378 | 379 | /* dn = in_odd[j * stride] - ((s1 + s2 + 2) >> 2); */ 380 | dn_0 = SUB(LOADU(in_odd + j * stride), 381 | SAR(ADD3(s1_0, s2_0, two), 2)); 382 | dn_1 = SUB(LOADU(in_odd + j * stride + VREG_INT_COUNT), 383 | SAR(ADD3(s1_1, s2_1, two), 2)); 384 | 385 | STORE(tmp + PARALLEL_COLS_53 * i, dc_0); 386 | STORE(tmp + PARALLEL_COLS_53 * i + VREG_INT_COUNT, dc_1); 387 | 388 | /* tmp[i + 1] = s1 + ((dn + dc) >> 1); */ 389 | STORE(tmp + PARALLEL_COLS_53 * (i + 1) + 0, 390 | ADD(s1_0, SAR(ADD(dn_0, dc_0), 1))); 391 | STORE(tmp + PARALLEL_COLS_53 * (i + 1) + VREG_INT_COUNT, 392 | ADD(s1_1, SAR(ADD(dn_1, dc_1), 1))); 393 | 394 | dc_0 = dn_0; 395 | s1_0 = s2_0; 396 | dc_1 = dn_1; 397 | s1_1 = s2_1; 398 | } 399 | STORE(tmp + PARALLEL_COLS_53 * i, dc_0); 400 | STORE(tmp + PARALLEL_COLS_53 * i + VREG_INT_COUNT, dc_1); 401 | 402 | if (!(len & 1)) { 403 | /*dn = in_odd[(len / 2 - 1) * stride] - ((s1 + 1) >> 1); */ 404 | dn_0 = SUB(LOADU(in_odd + (size_t)(len / 2 - 1) * stride), 405 | SAR(ADD3(s1_0, s1_0, two), 2)); 406 | dn_1 = SUB(LOADU(in_odd + (size_t)(len / 2 - 1) * stride + VREG_INT_COUNT), 407 | SAR(ADD3(s1_1, s1_1, two), 2)); 408 | 409 | /* tmp[len - 2] = s1 + ((dn + dc) >> 1); */ 410 | STORE(tmp + PARALLEL_COLS_53 * (len - 2) + 0, 411 | ADD(s1_0, SAR(ADD(dn_0, dc_0), 1))); 412 | STORE(tmp + PARALLEL_COLS_53 * (len - 2) + VREG_INT_COUNT, 413 | ADD(s1_1, SAR(ADD(dn_1, dc_1), 1))); 414 | 415 | STORE(tmp + PARALLEL_COLS_53 * (len - 1) + 0, dn_0); 416 | STORE(tmp + PARALLEL_COLS_53 * (len - 1) + VREG_INT_COUNT, dn_1); 417 | } else { 418 | STORE(tmp + PARALLEL_COLS_53 * (len - 1) + 0, ADD(s1_0, dc_0)); 419 | STORE(tmp + PARALLEL_COLS_53 * (len - 1) + VREG_INT_COUNT, 420 | ADD(s1_1, dc_1)); 421 | } 422 | 423 | opj_idwt53_v_final_memcpy(tiledp_col, tmp, len, stride); 424 | } 425 | 426 | #undef VREG 427 | #undef LOAD_CST 428 | #undef LOADU 429 | #undef LOAD 430 | #undef STORE 431 | #undef STOREU 432 | #undef ADD 433 | #undef ADD3 434 | #undef SUB 435 | #undef SAR 436 | 437 | #endif /* (defined(__SSE2__) || defined(__AVX2__)) && !defined(STANDARD_SLOW_VERSION) */ 438 | 439 | /** Vertical inverse 5x3 wavelet transform for one column, when top-most 440 | * pixel is on even coordinate */ 441 | static void opj_idwt3_v_cas0(icoeff_t* tmp, const i32 sn, const i32 len, icoeff_t* tiledp_col, const size_t stride) { 442 | i32 i, j; 443 | i32 d1c, d1n, s1n, s0c, s0n; 444 | 445 | ASSERT(len > 1); 446 | 447 | /* Performs lifting in one single iteration. Saves memory */ 448 | /* accesses and explicit interleaving. */ 449 | 450 | s1n = tiledp_col[0]; 451 | d1n = tiledp_col[(size_t)sn * stride]; 452 | s0n = s1n - ((d1n + 1) >> 1); 453 | 454 | for (i = 0, j = 0; i < (len - 3); i += 2, j++) { 455 | d1c = d1n; 456 | s0c = s0n; 457 | 458 | s1n = tiledp_col[(size_t)(j + 1) * stride]; 459 | d1n = tiledp_col[(size_t)(sn + j + 1) * stride]; 460 | 461 | s0n = s1n - ((d1c + d1n + 2) >> 2); 462 | 463 | tmp[i ] = s0c; 464 | tmp[i + 1] = d1c + ((s0c + s0n) >> 1); 465 | } 466 | 467 | tmp[i] = s0n; 468 | 469 | if (len & 1) { 470 | tmp[len - 1] = 471 | tiledp_col[(size_t)((len - 1) / 2) * stride] - 472 | ((d1n + 1) >> 1); 473 | tmp[len - 2] = d1n + ((s0n + tmp[len - 1]) >> 1); 474 | } else { 475 | tmp[len - 1] = d1n + s0n; 476 | } 477 | 478 | for (i = 0; i < len; ++i) { 479 | tiledp_col[(size_t)i * stride] = tmp[i]; 480 | } 481 | } 482 | 483 | /** Vertical inverse 5x3 wavelet transform for one column, when top-most 484 | * pixel is on odd coordinate */ 485 | static void opj_idwt3_v_cas1(icoeff_t* tmp, const i32 sn, const i32 len, icoeff_t* tiledp_col, const size_t stride) { 486 | i32 i, j; 487 | i32 s1, s2, dc, dn; 488 | const icoeff_t* in_even = &tiledp_col[(size_t)sn * stride]; 489 | const icoeff_t* in_odd = &tiledp_col[0]; 490 | 491 | ASSERT(len > 2); 492 | 493 | /* Performs lifting in one single iteration. Saves memory */ 494 | /* accesses and explicit interleaving. */ 495 | 496 | s1 = in_even[stride]; 497 | dc = in_odd[0] - ((in_even[0] + s1 + 2) >> 2); 498 | tmp[0] = in_even[0] + dc; 499 | for (i = 1, j = 1; i < (len - 2 - !(len & 1)); i += 2, j++) { 500 | 501 | s2 = in_even[(size_t)(j + 1) * stride]; 502 | 503 | dn = in_odd[(size_t)j * stride] - ((s1 + s2 + 2) >> 2); 504 | tmp[i ] = dc; 505 | tmp[i + 1] = s1 + ((dn + dc) >> 1); 506 | 507 | dc = dn; 508 | s1 = s2; 509 | } 510 | tmp[i] = dc; 511 | if (!(len & 1)) { 512 | dn = in_odd[(size_t)(len / 2 - 1) * stride] - ((s1 + 1) >> 1); 513 | tmp[len - 2] = s1 + ((dn + dc) >> 1); 514 | tmp[len - 1] = dn; 515 | } else { 516 | tmp[len - 1] = s1 + dc; 517 | } 518 | 519 | for (i = 0; i < len; ++i) { 520 | tiledp_col[(size_t)i * stride] = tmp[i]; 521 | } 522 | } 523 | 524 | /* */ 525 | /* Inverse vertical 5-3 wavelet transform in 1-D for several columns. */ 526 | /* */ 527 | /* Performs interleave, inverse wavelet transform and copy back to buffer */ 528 | static void opj_idwt53_v(const opj_dwt_t *dwt, icoeff_t* tiledp_col, size_t stride, i32 nb_cols) { 529 | const i32 sn = dwt->sn; 530 | const i32 len = sn + dwt->dn; 531 | if (dwt->cas == 0) { 532 | /* If len == 1, unmodified value */ 533 | 534 | #if (defined(__SSE2__) || defined(__AVX2__)) 535 | if (len > 1 && nb_cols == PARALLEL_COLS_53) { 536 | /* Same as below general case, except that thanks to SSE2/AVX2 */ 537 | /* we can efficiently process 8/16 columns in parallel */ 538 | opj_idwt53_v_cas0_mcols_SSE2_OR_AVX2(dwt->mem, sn, len, tiledp_col, stride); 539 | return; 540 | } 541 | #endif 542 | if (len > 1) { 543 | i32 c; 544 | for (c = 0; c < nb_cols; c++, tiledp_col++) { 545 | opj_idwt3_v_cas0(dwt->mem, sn, len, tiledp_col, stride); 546 | } 547 | return; 548 | } 549 | } else { 550 | if (len == 1) { 551 | i32 c; 552 | for (c = 0; c < nb_cols; c++, tiledp_col++) { 553 | tiledp_col[0] /= 2; 554 | } 555 | return; 556 | } 557 | 558 | if (len == 2) { 559 | i32 c; 560 | icoeff_t* out = dwt->mem; 561 | for (c = 0; c < nb_cols; c++, tiledp_col++) { 562 | i32 i; 563 | const icoeff_t* in_even = &tiledp_col[(size_t)sn * stride]; 564 | const icoeff_t* in_odd = &tiledp_col[0]; 565 | 566 | out[1] = in_odd[0] - ((in_even[0] + 1) >> 1); 567 | out[0] = in_even[0] + out[1]; 568 | 569 | for (i = 0; i < len; ++i) { 570 | tiledp_col[(size_t)i * stride] = out[i]; 571 | } 572 | } 573 | 574 | return; 575 | } 576 | 577 | #if (defined(__SSE2__) || defined(__AVX2__)) 578 | if (len > 2 && nb_cols == PARALLEL_COLS_53) { 579 | /* Same as below general case, except that thanks to SSE2/AVX2 */ 580 | /* we can efficiently process 8/16 columns in parallel */ 581 | opj_idwt53_v_cas1_mcols_SSE2_OR_AVX2(dwt->mem, sn, len, tiledp_col, stride); 582 | return; 583 | } 584 | #endif 585 | if (len > 2) { 586 | i32 c; 587 | for (c = 0; c < nb_cols; c++, tiledp_col++) { 588 | opj_idwt3_v_cas1(dwt->mem, sn, len, tiledp_col, stride); 589 | } 590 | return; 591 | } 592 | } 593 | } 594 | // End of openjp2 code. --------------------------------------------------------------------------------