├── include └── gvox │ ├── adapters │ ├── parse │ │ ├── octree.h │ │ ├── brickmap.h │ │ ├── gvox_raw.h │ │ ├── magicavoxel.h │ │ ├── gvox_palette.h │ │ ├── global_palette.h │ │ ├── run_length_encoding.h │ │ ├── kvx.h │ │ └── voxlap.h │ ├── output │ │ ├── stdout.h │ │ ├── file.h │ │ └── byte_buffer.h │ ├── serialize │ │ ├── octree.h │ │ ├── brickmap.h │ │ ├── gvox_raw.h │ │ ├── gvox_palette.h │ │ ├── global_palette.h │ │ ├── run_length_encoding.h │ │ ├── random_sample.h │ │ └── colored_text.h │ └── input │ │ ├── file.h │ │ └── byte_buffer.h │ └── gvox.h ├── .gitignore ├── cmake ├── toolchains │ ├── clang-x86_64-windows-msvc.cmake │ ├── gcc-x86_64-linux-gnu.cmake │ ├── clang-aarch64-macos-gnu.cmake │ ├── clang-x86_64-linux-gnu.cmake │ ├── emscripten-llvm-unknown-unknown.cmake │ ├── wasi-llvm-unknown-unknown.cmake │ ├── cl-x86_64-windows-msvc.cmake │ └── vcvars.cmake ├── packaging.cmake ├── vcpkg_triplets │ └── wasm32-wasisdk.cmake ├── warnings.cmake └── adapters.cmake ├── vcpkg.json ├── .clang-format ├── src ├── adapters │ ├── shared │ │ ├── gvox_brickmap.hpp │ │ ├── math_helpers.hpp │ │ ├── gvox_palette.hpp │ │ ├── gvox_octree.hpp │ │ └── thread_pool.hpp │ ├── output │ │ ├── stdout.cpp │ │ ├── file.cpp │ │ └── byte_buffer.cpp │ ├── input │ │ ├── byte_buffer.cpp │ │ └── file.cpp │ ├── serialize │ │ ├── random_sample.cpp │ │ ├── gvox_raw.cpp │ │ ├── gvox_run_length_encoding.cpp │ │ ├── gvox_global_palette.cpp │ │ └── gvox_brickmap.cpp │ └── parse │ │ ├── gvox_octree.cpp │ │ ├── gvox_raw.cpp │ │ ├── gvox_brickmap.cpp │ │ ├── gvox_global_palette.cpp │ │ ├── kvx.cpp │ │ ├── gvox_run_length_encoding.cpp │ │ ├── voxlap.cpp │ │ └── gvox_palette.cpp └── utils │ └── patch_wasm.h ├── portfile.cmake ├── LICENSE ├── .clang-tidy ├── tests ├── adapters │ ├── procedural.h │ └── procedural.cpp ├── CMakeLists.txt └── simple │ ├── parsing.cpp │ └── new_formats.cpp ├── .github └── workflows │ └── cmake.yml ├── README.md ├── CMakeLists.txt └── CMakePresets.json /include/gvox/adapters/parse/octree.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_OCTREE_PARSE_ADAPTER_H 2 | #define GVOX_OCTREE_PARSE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/output/stdout.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_STDOUT_OUTPUT_ADAPTER_H 2 | #define GVOX_STDOUT_OUTPUT_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/parse/brickmap.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_BRICKMAP_PARSE_ADAPTER_H 2 | #define GVOX_BRICKMAP_PARSE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/parse/gvox_raw.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_GVOX_RAW_PARSE_ADAPTER_H 2 | #define GVOX_GVOX_RAW_PARSE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/serialize/octree.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_OCTREE_SERIALIZE_ADAPTER_H 2 | #define GVOX_OCTREE_SERIALIZE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/parse/magicavoxel.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_MAGICAVOXEL_PARSE_ADAPTER_H 2 | #define GVOX_MAGICAVOXEL_PARSE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .out* 2 | .vs* 3 | build/ 4 | .cache/ 5 | 6 | imgui.ini 7 | *.dot 8 | 9 | # Voxel files 10 | *.gvox 11 | *.vox 12 | *.vxl 13 | *.mca 14 | *.nbt 15 | 16 | assets/ 17 | -------------------------------------------------------------------------------- /include/gvox/adapters/parse/gvox_palette.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_GVOX_PALETTE_PARSE_ADAPTER_H 2 | #define GVOX_GVOX_PALETTE_PARSE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/serialize/brickmap.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_BRICKMAP_SERIALIZE_ADAPTER_H 2 | #define GVOX_BRICKMAP_SERIALIZE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/serialize/gvox_raw.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_GVOX_RAW_SERIALIZE_ADAPTER_H 2 | #define GVOX_GVOX_RAW_SERIALIZE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/parse/global_palette.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_GLOBAL_PALETTE_PARSE_ADAPTER_H 2 | #define GVOX_GLOBAL_PALETTE_PARSE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/serialize/gvox_palette.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_GVOX_PALETTE_SERIALIZE_ADAPTER_H 2 | #define GVOX_GVOX_PALETTE_SERIALIZE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /cmake/toolchains/clang-x86_64-windows-msvc.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_SYSTEM_NAME Windows) 3 | set(CMAKE_SYSTEM_PROCESSOR x86_64) 4 | 5 | set(CMAKE_C_COMPILER_ID Clang) 6 | set(CMAKE_CXX_COMPILER_ID Clang) 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/serialize/global_palette.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_GLOBAL_PALETTE_SERIALIZE_ADAPTER_H 2 | #define GVOX_GLOBAL_PALETTE_SERIALIZE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/parse/run_length_encoding.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_RUN_LENGTH_ENCODING_PARSE_ADAPTER_H 2 | #define GVOX_RUN_LENGTH_ENCODING_PARSE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/output/file.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_FILE_OUTPUT_ADAPTER_H 2 | #define GVOX_FILE_OUTPUT_ADAPTER_H 3 | 4 | typedef struct { 5 | char const *filepath; 6 | } GvoxFileOutputAdapterConfig; 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /include/gvox/adapters/serialize/run_length_encoding.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_RUN_LENGTH_ENCODING_SERIALIZE_ADAPTER_H 2 | #define GVOX_RUN_LENGTH_ENCODING_SERIALIZE_ADAPTER_H 3 | 4 | // This adapter has no config 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gvox/adapters/input/file.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_FILE_INPUT_ADAPTER_H 2 | #define GVOX_FILE_INPUT_ADAPTER_H 3 | 4 | typedef struct { 5 | char const *filepath; 6 | size_t byte_offset; 7 | } GvoxFileInputAdapterConfig; 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /cmake/toolchains/gcc-x86_64-linux-gnu.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_SYSTEM_NAME Linux) 3 | set(CMAKE_SYSTEM_PROCESSOR x86_64) 4 | 5 | find_program(CMAKE_C_COMPILER gcc REQUIRED) 6 | find_program(CMAKE_CXX_COMPILER g++ REQUIRED) 7 | 8 | set(CMAKE_C_COMPILER_ID GNU) 9 | set(CMAKE_CXX_COMPILER_ID GNU) 10 | -------------------------------------------------------------------------------- /include/gvox/adapters/input/byte_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_BYTE_BUFFER_INPUT_ADAPTER_H 2 | #define GVOX_BYTE_BUFFER_INPUT_ADAPTER_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | uint8_t const *data; 8 | size_t size; 9 | } GvoxByteBufferInputAdapterConfig; 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /cmake/toolchains/clang-aarch64-macos-gnu.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_SYSTEM_NAME Darwin) 3 | set(CMAKE_SYSTEM_PROCESSOR aarch64) 4 | 5 | find_program(CMAKE_C_COMPILER clang REQUIRED) 6 | find_program(CMAKE_CXX_COMPILER clang++ REQUIRED) 7 | 8 | set(CMAKE_C_COMPILER_ID Clang) 9 | set(CMAKE_CXX_COMPILER_ID Clang) 10 | -------------------------------------------------------------------------------- /cmake/toolchains/clang-x86_64-linux-gnu.cmake: -------------------------------------------------------------------------------- 1 | 2 | set(CMAKE_SYSTEM_NAME Linux) 3 | set(CMAKE_SYSTEM_PROCESSOR x86_64) 4 | 5 | find_program(CMAKE_C_COMPILER clang REQUIRED) 6 | find_program(CMAKE_CXX_COMPILER clang++ REQUIRED) 7 | 8 | set(CMAKE_C_COMPILER_ID Clang) 9 | set(CMAKE_CXX_COMPILER_ID Clang) 10 | -------------------------------------------------------------------------------- /cmake/toolchains/emscripten-llvm-unknown-unknown.cmake: -------------------------------------------------------------------------------- 1 | 2 | include("$ENV{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake") 3 | 4 | unset(CMAKE_C_FLAGS CACHE) 5 | set(CMAKE_C_FLAGS "-emit-llvm" CACHE STRING "" FORCE) 6 | unset(CMAKE_CXX_FLAGS CACHE) 7 | set(CMAKE_CXX_FLAGS "-emit-llvm" CACHE STRING "" FORCE) 8 | -------------------------------------------------------------------------------- /include/gvox/adapters/serialize/random_sample.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_RANDOM_SAMPLE_SERIALIZE_ADAPTER_H 2 | #define GVOX_RANDOM_SAMPLE_SERIALIZE_ADAPTER_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | size_t sample_count; 9 | } GvoxRandomSampleSerializeAdapterConfig; 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/gvox/adapters/parse/kvx.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_KVX_PARSE_ADAPTER_H 2 | #define GVOX_KVX_PARSE_ADAPTER_H 3 | 4 | typedef struct { 5 | // This determines whether to make the insides of the map hollow or solid 6 | // By default, this is set to 1. 7 | uint8_t mipmaplevels; 8 | } GvoxKvxParseAdapterConfig; 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /include/gvox/adapters/output/byte_buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_BYTE_BUFFER_OUTPUT_ADAPTER_H 2 | #define GVOX_BYTE_BUFFER_OUTPUT_ADAPTER_H 3 | 4 | #include 5 | 6 | typedef struct { 7 | size_t *out_size; 8 | uint8_t **out_byte_buffer_ptr; 9 | 10 | void *(*allocate)(size_t size); 11 | } GvoxByteBufferOutputAdapterConfig; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gvox", 3 | "version-semver": "1.3.0", 4 | "description": "GVOX Voxel Format Library", 5 | "homepage": "https://github.com/GabeRundlett/gvox", 6 | "features": { 7 | "file-io": { 8 | "description": "Enable file IO library features" 9 | }, 10 | "tests": { 11 | "description": "Build Tests" 12 | } 13 | }, 14 | "builtin-baseline": "877e3dc2323a4d4c3c75e7168c22a0c4e921d4db" 15 | } 16 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | 3 | UseTab: Never 4 | TabWidth: 4 5 | IndentWidth: 4 6 | AccessModifierOffset: -2 7 | IndentCaseLabels: false 8 | ColumnLimit: 0 9 | BreakBeforeBraces: Attach 10 | 11 | SortIncludes: false 12 | SortUsingDeclarations: false 13 | 14 | NamespaceIndentation: All 15 | CompactNamespaces: true 16 | 17 | AllowShortIfStatementsOnASingleLine: false 18 | AllowShortCaseLabelsOnASingleLine: true 19 | AllowShortBlocksOnASingleLine: false -------------------------------------------------------------------------------- /src/adapters/shared/gvox_brickmap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct Brick { 7 | std::array voxels; 8 | }; 9 | 10 | union BrickmapHeader { 11 | struct Loaded { 12 | uint32_t heap_index : 31; 13 | uint32_t is_loaded : 1; 14 | } loaded; 15 | 16 | struct Unloaded { 17 | uint32_t lod_color : 24; 18 | uint32_t flags : 7; 19 | uint32_t is_loaded : 1; 20 | } unloaded; 21 | }; 22 | -------------------------------------------------------------------------------- /src/adapters/shared/math_helpers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | static constexpr auto ceil_log2(uint32_t x) -> uint32_t { 7 | constexpr auto const t = std::array{ 8 | 0xFFFF0000u, 9 | 0x0000FF00u, 10 | 0x000000F0u, 11 | 0x0000000Cu, 12 | 0x00000002u}; 13 | 14 | uint32_t y = (((x & (x - 1)) == 0) ? 0 : 1); 15 | int j = 16; 16 | 17 | for (uint32_t const i : t) { 18 | int const k = (((x & i) == 0) ? 0 : j); 19 | y += static_cast(k); 20 | x >>= k; 21 | j >>= 1; 22 | } 23 | 24 | return y; 25 | } 26 | -------------------------------------------------------------------------------- /cmake/toolchains/wasi-llvm-unknown-unknown.cmake: -------------------------------------------------------------------------------- 1 | 2 | file(TO_CMAKE_PATH "$ENV{WASI_SDK_PATH}" WASI_SDK_PREFIX) 3 | include("$ENV{WASI_SDK_PATH}/share/cmake/wasi-sdk.cmake") 4 | 5 | set(CMAKE_C_COMPILER_ID Clang) 6 | set(CMAKE_CXX_COMPILER_ID Clang) 7 | 8 | unset(CMAKE_C_FLAGS CACHE) 9 | set(CMAKE_C_FLAGS "-emit-llvm -D__wasi__=true -fno-exceptions -fvisibility=hidden --target=wasm32-unknown-unknown" CACHE STRING "" FORCE) 10 | unset(CMAKE_CXX_FLAGS CACHE) 11 | set(CMAKE_CXX_FLAGS "-emit-llvm -D__wasi__=true -fno-exceptions -fvisibility=hidden -D_LIBCPP_HAS_NO_FILESYSTEM_LIBRARY=false --target=wasm32-unknown-unknown" CACHE STRING "" FORCE) 12 | 13 | set(CMAKE_C_COMPILER_WORKS 1) 14 | set(CMAKE_CXX_COMPILER_WORKS 1) 15 | -------------------------------------------------------------------------------- /include/gvox/adapters/parse/voxlap.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_VOXLAP_PARSE_ADAPTER_H 2 | #define GVOX_VOXLAP_PARSE_ADAPTER_H 3 | 4 | typedef struct { 5 | // If the config is null, or the value is -1, the default will be used. 6 | 7 | // By default, the size of the map is 1024x1024x256 8 | uint32_t size_x; 9 | uint32_t size_y; 10 | uint32_t size_z; 11 | 12 | // This determines whether to make the insides of the map hollow or solid 13 | // By default, this is set to 1. 14 | uint8_t make_solid; 15 | 16 | // This designates whether the data should be parsed as Ace of Spades, 17 | // since Ace of Spades maps have no file header. 18 | // By default, this is set to 0. 19 | uint8_t is_ace_of_spades; 20 | } GvoxVoxlapParseAdapterConfig; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /portfile.cmake: -------------------------------------------------------------------------------- 1 | # Local-only code 2 | 3 | set(SOURCE_PATH "${CMAKE_CURRENT_LIST_DIR}") 4 | 5 | # Standard vcpkg stuff 6 | 7 | vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS 8 | FEATURES 9 | file-io WITH_FILE_IO 10 | ) 11 | 12 | set(GVOX_DEFINES "-DGVOX_ENABLE_MULTITHREADED_ADAPTERS=true" "-DGVOX_ENABLE_THREADSAFETY=true") 13 | 14 | if(WITH_FILE_IO) 15 | list(APPEND GVOX_DEFINES "-DGVOX_ENABLE_FILE_IO=true") 16 | endif() 17 | 18 | vcpkg_configure_cmake( 19 | SOURCE_PATH "${SOURCE_PATH}" 20 | PREFER_NINJA 21 | OPTIONS ${GVOX_DEFINES} 22 | ) 23 | 24 | vcpkg_install_cmake() 25 | vcpkg_fixup_cmake_targets() 26 | file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include") 27 | file(INSTALL "${SOURCE_PATH}/LICENSE" 28 | DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" 29 | RENAME copyright 30 | ) 31 | -------------------------------------------------------------------------------- /src/utils/patch_wasm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file includes some patched functions that are not linked properly when building for WASM. 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | extern "C" { 10 | uint32_t __imported_wasi_snapshot_preview1_fd_fdstat_get(uint32_t, uint32_t) { 11 | assert(false); 12 | return 0; 13 | } 14 | 15 | uint32_t __imported_wasi_snapshot_preview1_fd_close(uint32_t) { 16 | assert(false); 17 | return 0; 18 | } 19 | 20 | int32_t __imported_wasi_snapshot_preview1_fd_seek(int32_t, int64_t, int32_t, int32_t) { 21 | assert(false); 22 | return -1; 23 | } 24 | 25 | int32_t __imported_wasi_snapshot_preview1_fd_write(int32_t, int32_t, int32_t, int32_t) { 26 | assert(false); 27 | return -1; 28 | } 29 | 30 | int vfprintf(FILE*, const char *, va_list) { 31 | return -1; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /include/gvox/adapters/serialize/colored_text.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_COLORED_TEXT_SERIALIZE_ADAPTER_H 2 | #define GVOX_COLORED_TEXT_SERIALIZE_ADAPTER_H 3 | 4 | typedef enum { 5 | GVOX_COLORED_TEXT_SERIALIZE_ADAPTER_DOWNSCALE_MODE_NEAREST, 6 | GVOX_COLORED_TEXT_SERIALIZE_ADAPTER_DOWNSCALE_MODE_LINEAR, 7 | } GvoxColoredTextSerializeAdapterDownscaleMode; 8 | 9 | typedef struct { 10 | // By default, this should be 1. 11 | // If set to n, then each 'pixel' will represent n*n*n voxels 12 | uint32_t downscale_factor; 13 | // By default, this is ..._NEAREST. 14 | GvoxColoredTextSerializeAdapterDownscaleMode downscale_mode; 15 | // Doesn't make sense to have a default for this, when the default channel is color data 16 | uint32_t non_color_max_value; 17 | // By default, this should be 0 18 | // if set to 1, each layer will be printed below the last 19 | uint8_t vertical; 20 | } GvoxColoredTextSerializeAdapterConfig; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /src/adapters/output/stdout.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | // Base 7 | extern "C" void gvox_output_adapter_stdout_create(GvoxAdapterContext * /*unused*/, void const * /*unused*/) { 8 | } 9 | 10 | extern "C" void gvox_output_adapter_stdout_destroy(GvoxAdapterContext * /*unused*/) { 11 | } 12 | 13 | extern "C" void gvox_output_adapter_stdout_blit_begin(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 14 | } 15 | 16 | extern "C" void gvox_output_adapter_stdout_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 17 | } 18 | 19 | // General 20 | extern "C" void gvox_output_adapter_stdout_write(GvoxAdapterContext * /*unused*/, size_t /*unused*/, size_t size, void const *data) { 21 | auto str = std::string_view{static_cast(data), size}; 22 | std::cout << str; 23 | } 24 | 25 | extern "C" void gvox_output_adapter_stdout_reserve(GvoxAdapterContext * /*unused*/, size_t /*unused*/) { 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gabe Rundlett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmake/packaging.cmake: -------------------------------------------------------------------------------- 1 | 2 | include(CMakePackageConfigHelpers) 3 | file(WRITE ${CMAKE_BINARY_DIR}/config.cmake.in [=[ 4 | @PACKAGE_INIT@ 5 | include(${CMAKE_CURRENT_LIST_DIR}/gvox-targets.cmake) 6 | check_required_components(gvox) 7 | 8 | set(GVOX_FORMATS @GVOX_FORMATS@) 9 | ]=]) 10 | 11 | configure_package_config_file(${CMAKE_BINARY_DIR}/config.cmake.in 12 | ${CMAKE_CURRENT_BINARY_DIR}/gvox-config.cmake 13 | INSTALL_DESTINATION ${CMAKE_INSTALL_DATADIR}/gvox 14 | NO_SET_AND_CHECK_MACRO) 15 | write_basic_package_version_file( 16 | ${CMAKE_CURRENT_BINARY_DIR}/gvox-config-version.cmake 17 | VERSION ${PROJECT_VERSION} 18 | COMPATIBILITY SameMajorVersion) 19 | install( 20 | FILES 21 | ${CMAKE_CURRENT_BINARY_DIR}/gvox-config.cmake 22 | ${CMAKE_CURRENT_BINARY_DIR}/gvox-config-version.cmake 23 | DESTINATION 24 | ${CMAKE_INSTALL_DATADIR}/gvox) 25 | install(TARGETS gvox EXPORT gvox-targets) 26 | if(BUILD_SHARED_LIBS AND WIN32) 27 | install(FILES $ DESTINATION bin OPTIONAL) 28 | endif() 29 | install(EXPORT gvox-targets DESTINATION ${CMAKE_INSTALL_DATADIR}/gvox NAMESPACE gvox::) 30 | install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../include/ TYPE INCLUDE) 31 | -------------------------------------------------------------------------------- /cmake/vcpkg_triplets/wasm32-wasisdk.cmake: -------------------------------------------------------------------------------- 1 | set(VCPKG_ENV_PASSTHROUGH_UNTRACKED WASI_SDK_PATH PATH) 2 | 3 | if(NOT DEFINED ENV{WASI_SDK_PATH}) 4 | message(FATAL_ERROR "You must set the WASI_SDK_PATH env variable") 5 | else() 6 | file(TO_CMAKE_PATH "$ENV{WASI_SDK_PATH}" WASI_SDK_PREFIX) 7 | endif() 8 | 9 | if(NOT EXISTS "${WASI_SDK_PREFIX}/share/cmake/wasi-sdk.cmake") 10 | message(FATAL_ERROR "wasi-sdk.cmake toolchain file not found") 11 | endif() 12 | 13 | set(VCPKG_TARGET_ARCHITECTURE wasm32) 14 | set(VCPKG_CRT_LINKAGE dynamic) 15 | set(VCPKG_LIBRARY_LINKAGE static) 16 | set(VCPKG_CMAKE_SYSTEM_NAME WasiSDK) 17 | set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "${WASI_SDK_PREFIX}/share/cmake/wasi-sdk.cmake") 18 | 19 | set(VCPKG_C_FLAGS "-emit-llvm -D__wasi__=true -nostartfiles -fno-exceptions -Wl,--no-entry -Wl,--strip-all -Wl,--export-dynamic -Wl,--import-memory -fvisibility=hidden --target=wasm32-unknown-unknown") 20 | set(VCPKG_CXX_FLAGS "-emit-llvm -D__wasi__=true -nostartfiles -fno-exceptions -Wl,--no-entry -Wl,--strip-all -Wl,--export-dynamic -Wl,--import-memory -fvisibility=hidden -D_LIBCPP_HAS_NO_FILESYSTEM_LIBRARY=false --target=wasm32-unknown-unknown") 21 | set(VCPKG_CMAKE_CONFIGURE_OPTIONS -DCMAKE_C_COMPILER_WORKS=1 -DCMAKE_CXX_COMPILER_WORKS=1 -DWASI_SDK_PREFIX=${WASI_SDK_PREFIX}) 22 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: "*, 2 | -abseil-*, 3 | -altera-*, 4 | -android-*, 5 | -fuchsia-*, 6 | -google-*, 7 | -llvm*, 8 | -zircon-*, 9 | -readability-else-after-return, 10 | -readability-static-accessed-through-instance, 11 | -readability-avoid-const-params-in-decls, 12 | -cppcoreguidelines-non-private-member-variables-in-classes, 13 | -misc-non-private-member-variables-in-classes, 14 | -hicpp-uppercase-literal-suffix, 15 | -readability-uppercase-literal-suffix, 16 | -readability-identifier-length, 17 | -cppcoreguidelines-pro-type-reinterpret-cast, 18 | -performance-no-int-to-ptr, 19 | -bugprone-easily-swappable-parameters, 20 | -readability-simplify-boolean-expr, 21 | -hicpp-signed-bitwise, 22 | -cppcoreguidelines-avoid-magic-numbers, 23 | -readability-magic-numbers, 24 | -cppcoreguidelines-owning-memory, 25 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 26 | -cppcoreguidelines-no-malloc, 27 | -cppcoreguidelines-pro-type-union-access, 28 | -readability-function-cognitive-complexity, 29 | -hicpp-no-malloc 30 | " 31 | WarningsAsErrors: '' 32 | HeaderFilterRegex: '' 33 | FormatStyle: file 34 | -------------------------------------------------------------------------------- /src/adapters/shared/gvox_palette.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "math_helpers.hpp" 4 | 5 | static constexpr auto REGION_SIZE = size_t{8}; 6 | 7 | static constexpr auto get_mask(uint32_t bits_per_variant) -> uint32_t { 8 | return (~0u) >> (32 - bits_per_variant); 9 | } 10 | 11 | static constexpr auto calc_palette_region_size(size_t bits_per_variant) -> size_t { 12 | auto palette_region_size = (bits_per_variant * REGION_SIZE * REGION_SIZE * REGION_SIZE + 7) / 8; 13 | palette_region_size = (palette_region_size + 3) / 4; 14 | auto size = palette_region_size + 1; 15 | return size * 4; 16 | } 17 | 18 | static constexpr auto calc_block_size(size_t variant_n) -> size_t { 19 | return calc_palette_region_size(ceil_log2(static_cast(variant_n))) + sizeof(uint32_t) * variant_n; 20 | } 21 | 22 | static constexpr auto MAX_REGION_ALLOCATION_SIZE = REGION_SIZE * REGION_SIZE * REGION_SIZE * sizeof(uint32_t); 23 | static constexpr auto MAX_REGION_COMPRESSED_VARIANT_N = 24 | REGION_SIZE == 8 ? 367 : (REGION_SIZE == 16 ? 2559 : 0); 25 | 26 | static_assert(calc_block_size(MAX_REGION_COMPRESSED_VARIANT_N) <= MAX_REGION_ALLOCATION_SIZE); 27 | static_assert(calc_block_size(MAX_REGION_COMPRESSED_VARIANT_N + 1) > MAX_REGION_ALLOCATION_SIZE); 28 | 29 | struct ChannelHeader { 30 | uint32_t variant_n; 31 | uint32_t blob_offset; // if variant_n == 1, this is just the data 32 | }; 33 | -------------------------------------------------------------------------------- /src/adapters/shared/gvox_octree.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "math_helpers.hpp" 4 | #include 5 | #include 6 | 7 | union OctreeNode { 8 | struct Parent { 9 | uint32_t child_pointer : 24; 10 | uint32_t leaf_mask : 8; 11 | 12 | auto operator==(Parent const &other) const -> bool = default; 13 | } parent; 14 | 15 | struct Leaf { 16 | uint32_t color; 17 | 18 | auto operator==(Leaf const &other) const -> bool = default; 19 | } leaf; 20 | }; 21 | 22 | struct Octree { 23 | GvoxRegionRange range{}; 24 | std::vector nodes{}; 25 | 26 | uint32_t sample(OctreeNode::Parent const &self, uint32_t x, uint32_t y, uint32_t z, uint32_t depth = 0) const { 27 | auto child_size = (1u << ceil_log2(std::max({range.extent.x, range.extent.y, range.extent.z}))) / (2u << depth); 28 | if (child_size == 0) { 29 | return 0; 30 | } 31 | auto cx = x / child_size; 32 | auto cy = y / child_size; 33 | auto cz = z / child_size; 34 | auto child_offset = cx + cy * 2 + cz * 4; 35 | auto const &child = nodes[self.child_pointer + child_offset]; 36 | bool is_leaf = (self.leaf_mask >> child_offset) & 1; 37 | if (is_leaf) { 38 | return child.leaf.color; 39 | } else { 40 | return sample(child.parent, x - cx * child_size, y - cy * child_size, z - cz * child_size, depth + 1); 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /tests/adapters/procedural.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_PROCEDURAL_PARSE_ADAPTER_H 2 | #define GVOX_PROCEDURAL_PARSE_ADAPTER_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | void procedural_create(GvoxAdapterContext *ctx, void const *config); 11 | void procedural_destroy(GvoxAdapterContext *ctx); 12 | void procedural_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 13 | void procedural_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx); 14 | 15 | GvoxParseAdapterDetails procedural_query_details(void); 16 | uint32_t procedural_query_region_flags(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 17 | 18 | GvoxRegion procedural_load_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 19 | void procedural_unload_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion *region); 20 | GvoxRegionRange procedural_query_parsable_range(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx); 21 | GvoxSample procedural_sample_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region, GvoxOffset3D const *offset, uint32_t channel_id); 22 | 23 | void procedural_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 24 | 25 | // This adapter has no config 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | function(GVOX_CREATE_TEST) 3 | set(OPT_ARGS UNUSED) 4 | set(SNG_ARGS FILE LANG) 5 | set(MUL_ARGS FOLDER LIBS) 6 | cmake_parse_arguments(PARSE_ARGV 0 GCT "${OPT_ARGS}" "${SNG_ARGS}" "${MUL_ARGS}") 7 | 8 | list(JOIN GCT_FOLDER "_" FOLDER_NAME) 9 | list(JOIN GCT_FOLDER "/" FOLDER_PATH) 10 | 11 | set(TGT gvox_test_${FOLDER_NAME}_${GCT_FILE}) 12 | add_executable(${TGT} "${FOLDER_PATH}/${GCT_FILE}.${GCT_LANG}") 13 | target_link_libraries(${TGT} PRIVATE gvox::gvox ${GCT_LIBS}) 14 | target_include_directories(${TGT} PRIVATE "${CMAKE_CURRENT_LIST_DIR}") 15 | target_compile_definitions(${TGT} PRIVATE GVOX_SAMPLE_PATH="${CMAKE_CURRENT_LIST_DIR}/${FOLDER_PATH}") 16 | 17 | if(GVOX_TESTS_DISABLE_WINDOWS_CONSOLE) 18 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 19 | target_link_options(${TGT} 20 | PRIVATE "-Wl,/ENTRY:mainCRTStartup,/SUBSYSTEM:WINDOWS" 21 | ) 22 | endif() 23 | endif() 24 | 25 | set_project_sanitizers(${TGT}) 26 | endfunction() 27 | 28 | add_library(procedural_parse_adapter "adapters/procedural.cpp") 29 | target_include_directories(procedural_parse_adapter PUBLIC "${CMAKE_CURRENT_LIST_DIR}") 30 | target_link_libraries(procedural_parse_adapter PUBLIC gvox::gvox) 31 | 32 | GVOX_CREATE_TEST( 33 | FOLDER simple 34 | FILE main 35 | LANG c 36 | LIBS procedural_parse_adapter 37 | ) 38 | 39 | GVOX_CREATE_TEST( 40 | FOLDER simple 41 | FILE new_formats 42 | LANG cpp 43 | LIBS procedural_parse_adapter 44 | ) 45 | 46 | GVOX_CREATE_TEST( 47 | FOLDER simple 48 | FILE parsing 49 | LANG cpp 50 | LIBS 51 | ) 52 | -------------------------------------------------------------------------------- /cmake/toolchains/cl-x86_64-windows-msvc.cmake: -------------------------------------------------------------------------------- 1 | # Don't set the MSVC compiler.. This works when opening the folder in Visual Studio 2 | include(${CMAKE_CURRENT_LIST_DIR}/vcvars.cmake) 3 | 4 | find_program(CMAKE_C_COMPILER cl REQUIRED HINTS ${MSVC_ENV_Path}) 5 | find_program(CMAKE_CXX_COMPILER cl REQUIRED HINTS ${MSVC_ENV_Path}) 6 | find_program(CMAKE_MT mt REQUIRED HINTS ${MSVC_ENV_Path}) 7 | find_program(CMAKE_RC_COMPILER rc REQUIRED HINTS ${MSVC_ENV_Path}) 8 | 9 | set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MSVC) 10 | set(CMAKE_C_COMPILER_FRONTEND_VARIANT MSVC) 11 | 12 | list(APPEND CMAKE_C_STANDARD_INCLUDE_DIRECTORIES ${MSVC_ENV_INCLUDE}) 13 | list(REMOVE_DUPLICATES CMAKE_C_STANDARD_INCLUDE_DIRECTORIES) 14 | set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_C_STANDARD_INCLUDE_DIRECTORIES} CACHE STRING "") 15 | 16 | list(APPEND CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${MSVC_ENV_INCLUDE}) 17 | list(REMOVE_DUPLICATES CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES) 18 | set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES} CACHE STRING "") 19 | 20 | list(APPEND CMAKE_RC_STANDARD_INCLUDE_DIRECTORIES ${MSVC_ENV_INCLUDE}) 21 | list(REMOVE_DUPLICATES CMAKE_RC_STANDARD_INCLUDE_DIRECTORIES) 22 | set(CMAKE_RC_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_RC_STANDARD_INCLUDE_DIRECTORIES} CACHE STRING "") 23 | 24 | foreach(libpath ${MSVC_ENV_LIBPATH} ${MSVC_ENV_LIB}) 25 | set(CMAKE_SHARED_LINKER_FLAGS_INIT "${CMAKE_SHARED_LINKER_FLAGS_INIT} \"/LIBPATH:${libpath}\"") 26 | set(CMAKE_MODULE_LINKER_FLAGS_INIT "${CMAKE_MODULE_LINKER_FLAGS_INIT} \"/LIBPATH:${libpath}\"") 27 | set(CMAKE_EXE_LINKER_FLAGS_INIT "${CMAKE_EXE_LINKER_FLAGS_INIT} \"/LIBPATH:${libpath}\"") 28 | endforeach() 29 | 30 | list(APPEND LINK_DIRECTORIES $ENV{LIB} $ENV{LIBPATH}) 31 | list(REMOVE_DUPLICATES LINK_DIRECTORIES) 32 | set(LINK_DIRECTORIES ${LINK_DIRECTORIES} CACHE STRING "") 33 | link_directories(BEFORE ${LINK_DIRECTORIES}) 34 | 35 | list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES 36 | MSVC_ENV_INCLUDE 37 | MSVC_ENV_LIBPATH 38 | MSVC_ENV_VAR_NAMES 39 | MSVC_ENV_Path 40 | MSVC_ENV_LIB 41 | ) 42 | -------------------------------------------------------------------------------- /src/adapters/input/byte_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | struct ByteBufferInputUserState { 12 | std::vector bytes{}; 13 | }; 14 | 15 | // Base 16 | extern "C" void gvox_input_adapter_byte_buffer_create(GvoxAdapterContext *ctx, void const *config) { 17 | auto *user_state_ptr = malloc(sizeof(ByteBufferInputUserState)); 18 | auto &user_state = *(new (user_state_ptr) ByteBufferInputUserState()); 19 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 20 | const auto &user_config = *static_cast(config); 21 | user_state.bytes.resize(user_config.size); 22 | std::copy(user_config.data, user_config.data + user_config.size, user_state.bytes.begin()); 23 | } 24 | 25 | extern "C" void gvox_input_adapter_byte_buffer_destroy(GvoxAdapterContext *ctx) { 26 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 27 | user_state.~ByteBufferInputUserState(); 28 | free(&user_state); 29 | } 30 | 31 | extern "C" void gvox_input_adapter_byte_buffer_blit_begin(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 32 | } 33 | 34 | extern "C" void gvox_input_adapter_byte_buffer_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 35 | } 36 | 37 | // General 38 | extern "C" void gvox_input_adapter_byte_buffer_read(GvoxAdapterContext *ctx, size_t position, size_t size, void *data) { 39 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 40 | if (position + size > user_state.bytes.size()) { 41 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_INPUT_ADAPTER, "Tried reading past the end of the provided input buffer"); 42 | return; 43 | } 44 | std::copy(user_state.bytes.data() + position, user_state.bytes.data() + position + size, static_cast(data)); 45 | } 46 | -------------------------------------------------------------------------------- /src/adapters/input/file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #if GVOX_ENABLE_MULTITHREADED_ADAPTERS && GVOX_ENABLE_THREADSAFETY 13 | #include 14 | #endif 15 | 16 | struct FileInputUserState { 17 | std::filesystem::path path{}; 18 | std::ifstream file{}; 19 | size_t byte_offset{}; 20 | #if GVOX_ENABLE_MULTITHREADED_ADAPTERS && GVOX_ENABLE_THREADSAFETY 21 | std::mutex mtx{}; 22 | #endif 23 | }; 24 | 25 | // Base 26 | extern "C" void gvox_input_adapter_file_create(GvoxAdapterContext *ctx, void const *config) { 27 | auto *user_state_ptr = malloc(sizeof(FileInputUserState)); 28 | auto &user_state = *(new (user_state_ptr) FileInputUserState()); 29 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 30 | const auto &user_config = *static_cast(config); 31 | user_state.byte_offset = user_config.byte_offset; 32 | user_state.path = user_config.filepath; 33 | } 34 | 35 | extern "C" void gvox_input_adapter_file_destroy(GvoxAdapterContext *ctx) { 36 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 37 | user_state.~FileInputUserState(); 38 | free(&user_state); 39 | } 40 | 41 | extern "C" void gvox_input_adapter_file_blit_begin(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 42 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 43 | user_state.file.open(user_state.path, std::ios::binary); 44 | } 45 | 46 | extern "C" void gvox_input_adapter_file_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) { 47 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 48 | user_state.file.close(); 49 | } 50 | 51 | // General 52 | extern "C" void gvox_input_adapter_file_read(GvoxAdapterContext *ctx, size_t position, size_t size, void *data) { 53 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 54 | #if GVOX_ENABLE_MULTITHREADED_ADAPTERS && GVOX_ENABLE_THREADSAFETY 55 | [[maybe_unused]] auto lock = std::lock_guard{user_state.mtx}; 56 | #endif 57 | user_state.file.seekg(static_cast(position), std::ios_base::beg); 58 | user_state.file.read(static_cast(data), static_cast(size)); 59 | } 60 | -------------------------------------------------------------------------------- /src/adapters/output/file.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct OutputFileUserState { 14 | std::filesystem::path path{}; 15 | std::vector bytes{}; 16 | }; 17 | 18 | // Base 19 | extern "C" void gvox_output_adapter_file_create(GvoxAdapterContext *ctx, void const *config) { 20 | auto *user_state_ptr = malloc(sizeof(OutputFileUserState)); 21 | auto &user_state = *(new (user_state_ptr) OutputFileUserState()); 22 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 23 | if (config != nullptr) { 24 | const auto *user_config = static_cast(config); 25 | user_state.path = user_config->filepath; 26 | } else { 27 | user_state.path = "gvox_file_out.bin"; 28 | } 29 | } 30 | 31 | extern "C" void gvox_output_adapter_file_destroy(GvoxAdapterContext *ctx) { 32 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 33 | user_state.~OutputFileUserState(); 34 | free(&user_state); 35 | } 36 | 37 | extern "C" void gvox_output_adapter_file_blit_begin(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 38 | } 39 | 40 | extern "C" void gvox_output_adapter_file_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) { 41 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 42 | auto file = std::ofstream(user_state.path, std::ios_base::binary); 43 | file.write(reinterpret_cast(user_state.bytes.data()), static_cast(user_state.bytes.size())); 44 | } 45 | 46 | // General 47 | extern "C" void gvox_output_adapter_file_reserve(GvoxAdapterContext *ctx, size_t size) { 48 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 49 | if (size > user_state.bytes.size()) { 50 | user_state.bytes.resize(size); 51 | } 52 | } 53 | 54 | extern "C" void gvox_output_adapter_file_write(GvoxAdapterContext *ctx, size_t position, size_t size, void const *data) { 55 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 56 | gvox_output_adapter_file_reserve(ctx, position + size); 57 | const auto *bytes = static_cast(data); 58 | std::copy(bytes, bytes + size, user_state.bytes.data() + position); 59 | } 60 | -------------------------------------------------------------------------------- /src/adapters/shared/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define ENABLE_THREAD_POOL (GVOX_ENABLE_MULTITHREADED_ADAPTERS && GVOX_ENABLE_THREADSAFETY) 6 | 7 | #if ENABLE_THREAD_POOL 8 | #include 9 | #include 10 | #include 11 | #include 12 | #endif 13 | 14 | namespace gvox_detail::thread_pool { 15 | struct ThreadPool { 16 | void start() { 17 | #if ENABLE_THREAD_POOL 18 | uint32_t const num_threads = std::thread::hardware_concurrency(); 19 | threads.resize(num_threads); 20 | for (uint32_t i = 0; i < num_threads; i++) { 21 | threads.at(i) = std::thread(&ThreadPool::thread_loop, this); 22 | } 23 | #endif 24 | } 25 | void enqueue(std::function const &job) { 26 | #if ENABLE_THREAD_POOL 27 | { 28 | std::unique_lock lock(queue_mutex); 29 | jobs.push(job); 30 | } 31 | mutex_condition.notify_one(); 32 | #else 33 | job(); 34 | #endif 35 | } 36 | void stop() { 37 | #if ENABLE_THREAD_POOL 38 | { 39 | std::unique_lock lock(queue_mutex); 40 | should_terminate = true; 41 | } 42 | mutex_condition.notify_all(); 43 | for (std::thread &active_thread : threads) { 44 | active_thread.join(); 45 | } 46 | threads.clear(); 47 | #endif 48 | } 49 | auto busy() -> bool { 50 | #if ENABLE_THREAD_POOL 51 | bool pool_busy; 52 | { 53 | std::unique_lock lock(queue_mutex); 54 | pool_busy = !jobs.empty(); 55 | } 56 | return pool_busy; 57 | #else 58 | return false; 59 | #endif 60 | } 61 | 62 | private: 63 | void thread_loop() { 64 | #if ENABLE_THREAD_POOL 65 | while (true) { 66 | std::function job; 67 | { 68 | std::unique_lock lock(queue_mutex); 69 | mutex_condition.wait(lock, [this] { 70 | return !jobs.empty() || should_terminate; 71 | }); 72 | if (should_terminate) { 73 | return; 74 | } 75 | job = jobs.front(); 76 | jobs.pop(); 77 | } 78 | job(); 79 | } 80 | #endif 81 | } 82 | #if ENABLE_THREAD_POOL 83 | bool should_terminate = false; 84 | std::mutex queue_mutex; 85 | std::condition_variable mutex_condition; 86 | std::vector threads; 87 | std::queue> jobs; 88 | #endif 89 | }; 90 | } // namespace gvox_detail::thread_pool 91 | -------------------------------------------------------------------------------- /src/adapters/output/byte_buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | struct ByteBufferOutputUserState { 12 | GvoxByteBufferOutputAdapterConfig config{}; 13 | std::vector bytes{}; 14 | }; 15 | 16 | // Base 17 | extern "C" void gvox_output_adapter_byte_buffer_create(GvoxAdapterContext *ctx, void const *config) { 18 | auto *user_state_ptr = malloc(sizeof(ByteBufferOutputUserState)); 19 | auto &user_state = *(new (user_state_ptr) ByteBufferOutputUserState()); 20 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 21 | if (config != nullptr) { 22 | user_state.config = *static_cast(config); 23 | } else { 24 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_OUTPUT_ADAPTER, "Can't use this 'bytes' output adapter without a config"); 25 | } 26 | } 27 | 28 | extern "C" void gvox_output_adapter_byte_buffer_destroy(GvoxAdapterContext *ctx) { 29 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 30 | user_state.~ByteBufferOutputUserState(); 31 | free(&user_state); 32 | } 33 | 34 | extern "C" void gvox_output_adapter_byte_buffer_blit_begin(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 35 | } 36 | 37 | extern "C" void gvox_output_adapter_byte_buffer_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) { 38 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 39 | void *bytes = nullptr; 40 | // NOTE: This needs to be manually cleaned up by the user! 41 | if (user_state.config.allocate != nullptr) { 42 | bytes = user_state.config.allocate(user_state.bytes.size()); 43 | } else { 44 | bytes = malloc(user_state.bytes.size()); 45 | } 46 | std::copy(user_state.bytes.begin(), user_state.bytes.end(), static_cast(bytes)); 47 | *user_state.config.out_byte_buffer_ptr = static_cast(bytes); 48 | *user_state.config.out_size = user_state.bytes.size(); 49 | } 50 | 51 | // General 52 | extern "C" void gvox_output_adapter_byte_buffer_write(GvoxAdapterContext *ctx, size_t position, size_t size, void const *data) { 53 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 54 | if (position + size > user_state.bytes.size()) { 55 | user_state.bytes.resize(position + size); 56 | } 57 | const auto *bytes = static_cast(data); 58 | std::copy(bytes, bytes + size, user_state.bytes.data() + position); 59 | } 60 | 61 | extern "C" void gvox_output_adapter_byte_buffer_reserve(GvoxAdapterContext *ctx, size_t size) { 62 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 63 | if (size > user_state.bytes.size()) { 64 | user_state.bytes.resize(size); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | on: 3 | push: 4 | branches: ["master"] 5 | pull_request: 6 | branches: ["master"] 7 | jobs: 8 | update-package: 9 | name: Trigger Package Updates 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: "Dispatch" 13 | uses: peter-evans/repository-dispatch@v2 14 | with: 15 | token: ${{ secrets.REGISTRY_TOKEN }} 16 | repository: GabeRundlett/vcpkg-registry 17 | event-type: update-all 18 | client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' 19 | build-linux: 20 | name: Build Linux 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: lukka/get-cmake@latest 25 | - uses: seanmiddleditch/gha-setup-ninja@v3 26 | - name: Setup Vulkan SDK 27 | uses: humbletim/setup-vulkan-sdk@v1.2.0 28 | with: 29 | vulkan-query-version: latest 30 | vulkan-use-cache: true 31 | - name: Cache apt-install 32 | uses: daaku/gh-action-apt-install@v4 33 | with: 34 | packages: build-essential xorg-dev libtinfo5 curl zip unzip tar wayland-protocols libxkbcommon-dev 35 | - name: run-vcpkg 36 | uses: lukka/run-vcpkg@v10.5 37 | with: 38 | vcpkgGitCommitId: 877e3dc2323a4d4c3c75e7168c22a0c4e921d4db 39 | - name: Configure CMake Clang 40 | run: cmake --preset=clang-x86_64-linux-gnu 41 | - name: Build Clang Debug 42 | run: cmake --build --preset=clang-x86_64-linux-gnu-debug 43 | - name: Build Clang Release 44 | run: cmake --build --preset=clang-x86_64-linux-gnu-release 45 | - name: Configure CMake GCC 46 | run: cmake --preset=gcc-x86_64-linux-gnu 47 | - name: Build GCC Debug 48 | run: cmake --build --preset=gcc-x86_64-linux-gnu-debug 49 | - name: Build GCC Release 50 | run: cmake --build --preset=gcc-x86_64-linux-gnu-release 51 | build-windows: 52 | name: Build Windows 53 | runs-on: windows-latest 54 | steps: 55 | - uses: actions/checkout@v3 56 | - uses: microsoft/setup-msbuild@v1.0.2 57 | - uses: lukka/get-cmake@latest 58 | - uses: seanmiddleditch/gha-setup-ninja@v3 59 | - name: Setup Vulkan SDK 60 | uses: humbletim/setup-vulkan-sdk@v1.2.0 61 | with: 62 | vulkan-query-version: latest 63 | vulkan-use-cache: true 64 | - name: run-vcpkg 65 | uses: lukka/run-vcpkg@v10.5 66 | with: 67 | vcpkgGitCommitId: 877e3dc2323a4d4c3c75e7168c22a0c4e921d4db 68 | # - name: Configure CMake Clang 69 | # run: cmake --preset=clang-x86_64-windows-msvc 70 | # - name: Build Clang Debug 71 | # run: cmake --build --preset=clang-x86_64-windows-msvc-debug 72 | # - name: Build Clang Release 73 | # run: cmake --build --preset=clang-x86_64-windows-msvc-release 74 | - name: Configure CMake CL.exe 75 | run: cmake --preset=cl-x86_64-windows-msvc 76 | - name: Build CL.exe Debug 77 | run: cmake --build --preset=cl-x86_64-windows-msvc-debug 78 | - name: Build CL.exe Release 79 | run: cmake --build --preset=cl-x86_64-windows-msvc-release 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repo is the home of the Gvox library 2 | 3 | `.gvox` is a meta-format that allows for several voxel data structures to co-exist within a single file. 4 | 5 | Gvox, the **G**eneral **vox**el format translation library, is what ties it all together, supplying parsing, conversion and serialization mechanisms to the consumer. 6 | 7 | ## Goals 8 | * Gvox should be accessible to everyone making a voxel engine, including but not limited to 9 | * People who are targeting the Web, using Rust or other WASM compatible languages 10 | * People using any language that has simple C bindability, such as ZIG, C++, Java and many more 11 | 12 | This is why the library uses a C99 interface, and I plan on creating bindings for as many projects that want it (Open an issue on GitHub, and we can work together!) 13 | 14 | * Gvox should be extremely extensible, so anyone can create their own formats that are then automatically convertible to any of the existing formats supported by GVOX! 15 | * Gvox should NOT have an internal intermediary data representation (such as a managed flat array of voxels), so that there is no wasted memory consumption during translation 16 | * Gvox should be multi-threadable 17 | * Gvox should allow for at least 2D, 3D, and 4D grids 18 | 19 | ## Building 20 | For now, you must have the following things installed to build the repository 21 | * A C++ compiler 22 | * CMake (3.21 or higher) 23 | * Ninja build 24 | * vcpkg (plus the VCPKG_ROOT environment variable) 25 | 26 | Once you have these things installed, you should be able to build just by running these commands in the root directory of the repository 27 | 28 | ### Windows 29 | 30 | ``` 31 | cmake --preset=cl-x86_64-windows-msvc 32 | cmake --build --preset=cl-x86_64-windows-msvc-debug 33 | ``` 34 | 35 | ### Linux 36 | 37 | ``` 38 | cmake --preset=gcc-x86_64-linux-gnu 39 | cmake --build --preset=gcc-x86_64-linux-gnu-debug 40 | ``` 41 | 42 | ### WASM 43 | Building for WASM is a little more involved. There are two methods, the Wasi SDK or Emscripten. 44 | 45 | #### Wasi SDK 46 | Install the Wasi SDK from [here](https://github.com/WebAssembly/wasi-sdk/releases). For Windows, that means downloading the MinGW build of it, and adding the environment variable `WASI_SDK_PATH` to be the extracted folder containing `bin/` `lib/` and `share/`. 47 | 48 | TODO: when the library has any dependencies, it must be the case that a Wasi community triplet is added to vcpkg 49 | 50 | Once that's done and your environment is successfully refreshed in your terminal - you can run CMake normally: 51 | ``` 52 | cmake --preset=wasi-wasm32-unknown-unknown 53 | cmake --build --preset=wasi-wasm32-unknown-unknown-debug 54 | ``` 55 | 56 | #### Emscripten 57 | Install emscripten by following [the instructions on their website](https://emscripten.org/docs/getting_started/downloads.html). (On Windows, you may need to add the `EMSDK` environment variable manually, because I don't know a better way). 58 | 59 | Again, once your environment is successfully refreshed, you can just run CMake normally: 60 | ``` 61 | cmake --preset=emscripten-wasm32-unknown-unknown 62 | cmake --build --preset=emscripten-wasm32-unknown-unknown-debug 63 | ``` 64 | 65 | The plan in the future is to have language bindings accessible to as many languages I can. 66 | -------------------------------------------------------------------------------- /cmake/warnings.cmake: -------------------------------------------------------------------------------- 1 | function(set_project_warnings project_name) 2 | option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) 3 | 4 | set(MSVC_WARNINGS 5 | /W4 # Baseline reasonable warnings 6 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data 7 | /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 8 | /w14263 # 'function': member function does not override any base class virtual member function 9 | /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not 10 | # be destructed correctly 11 | /w14287 # 'operator': unsigned/negative constant mismatch 12 | /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside 13 | # the for-loop scope 14 | /w14296 # 'operator': expression is always 'boolean_value' 15 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 16 | /w14545 # expression before comma evaluates to a function which is missing an argument list 17 | /w14546 # function call before comma missing argument list 18 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 19 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 20 | /w14555 # expression has no effect; expected expression with side- effect 21 | /w14619 # pragma warning: there is no warning number 'number' 22 | /w14640 # Enable warning on thread un-safe static member initialization 23 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. 24 | /w14905 # wide string literal cast to 'LPSTR' 25 | /w14906 # string literal cast to 'LPWSTR' 26 | /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied 27 | /permissive- # standards conformance mode for MSVC compiler. 28 | ) 29 | 30 | set(CLANG_WARNINGS 31 | -Wall 32 | -Wextra # reasonable and standard 33 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 34 | -Wunused # warn on anything being unused 35 | -Wpedantic # warn if non-standard C++ is used 36 | -Wconversion # warn on type conversions that may lose data 37 | -Wsign-conversion # warn on sign conversions 38 | -Wnull-dereference # warn if a null dereference is detected 39 | -Wdouble-promotion # warn if float is implicit promoted to double 40 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 41 | -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation 42 | ) 43 | 44 | if(WARNINGS_AS_ERRORS) 45 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 46 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 47 | endif() 48 | 49 | set(GCC_WARNINGS 50 | ${CLANG_WARNINGS} 51 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 52 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 53 | -Wduplicated-branches # warn if if / else branches have duplicated code 54 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 55 | -Wno-strict-aliasing # DISABLE THIS BOGUS 56 | ) 57 | 58 | if(MSVC) 59 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 60 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 61 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 62 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 63 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 64 | else() 65 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 66 | endif() 67 | 68 | target_compile_options(${project_name} PUBLIC $) 69 | 70 | endfunction() -------------------------------------------------------------------------------- /src/adapters/serialize/random_sample.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct RandomSampleUserState { 13 | uint32_t value{}; 14 | std::chrono::nanoseconds duration{}; 15 | GvoxRandomSampleSerializeAdapterConfig config{}; 16 | }; 17 | 18 | // Base 19 | extern "C" void gvox_serialize_adapter_random_sample_create(GvoxAdapterContext *ctx, void const *config) { 20 | auto *user_state_ptr = malloc(sizeof(RandomSampleUserState)); 21 | [[maybe_unused]] auto &user_state = *(new (user_state_ptr) RandomSampleUserState()); 22 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 23 | if (config != nullptr) { 24 | user_state.config = *static_cast(config); 25 | } else { 26 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_OUTPUT_ADAPTER, "Can't use this 'bytes' output adapter without a config"); 27 | } 28 | } 29 | 30 | extern "C" void gvox_serialize_adapter_random_sample_destroy(GvoxAdapterContext *ctx) { 31 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 32 | user_state.~RandomSampleUserState(); 33 | free(&user_state); 34 | } 35 | 36 | extern "C" void gvox_serialize_adapter_random_sample_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 37 | } 38 | 39 | extern "C" void gvox_serialize_adapter_random_sample_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx) { 40 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 41 | auto duration = std::chrono::duration(user_state.duration).count(); 42 | gvox_output_write(blit_ctx, 0, sizeof(duration), &duration); 43 | } 44 | 45 | // Serialize Driven 46 | extern "C" void gvox_serialize_adapter_random_sample_serialize_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 47 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 48 | 49 | std::vector channels; 50 | channels.resize(static_cast(std::popcount(channel_flags))); 51 | uint32_t next_channel = 0; 52 | for (uint8_t channel_i = 0; channel_i < 32; ++channel_i) { 53 | if ((channel_flags & (1u << channel_i)) != 0) { 54 | channels[next_channel] = channel_i; 55 | ++next_channel; 56 | } 57 | } 58 | 59 | auto dev = std::random_device{}; 60 | auto rng = std::mt19937(dev()); 61 | auto x_dist = std::uniform_int_distribution(0, range->extent.x - 1); 62 | auto y_dist = std::uniform_int_distribution(0, range->extent.y - 1); 63 | auto z_dist = std::uniform_int_distribution(0, range->extent.z - 1); 64 | auto c_dist = std::uniform_int_distribution(0, channels.size() - 1); 65 | 66 | for (size_t i = 0; i < user_state.config.sample_count; ++i) { 67 | // get random pos 68 | auto pos = GvoxOffset3D{ 69 | .x = range->offset.x + static_cast(x_dist(rng)), 70 | .y = range->offset.y + static_cast(y_dist(rng)), 71 | .z = range->offset.z + static_cast(z_dist(rng)), 72 | }; 73 | // get random channel 74 | auto channel_i = c_dist(rng); 75 | 76 | auto const sample_range = GvoxRegionRange{ 77 | .offset = pos, 78 | .extent = GvoxExtent3D{1, 1, 1}, 79 | }; 80 | using Clock = std::chrono::high_resolution_clock; 81 | auto t0 = Clock::now(); 82 | auto region = gvox_load_region_range(blit_ctx, &sample_range, 1u << channels[channel_i]); 83 | auto sample = gvox_sample_region(blit_ctx, ®ion, &pos, channels[channel_i]); 84 | auto t1 = Clock::now(); 85 | if (sample.is_present == 0u) { 86 | sample.data = 0u; 87 | } 88 | user_state.value += sample.data; 89 | user_state.duration += t1 - t0; 90 | gvox_unload_region_range(blit_ctx, ®ion, &sample_range); 91 | } 92 | } 93 | 94 | // Parse Driven 95 | extern "C" void gvox_serialize_adapter_random_sample_receive_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region) { 96 | // Shouldn't be called 97 | } 98 | -------------------------------------------------------------------------------- /cmake/toolchains/vcvars.cmake: -------------------------------------------------------------------------------- 1 | function(msvc_inherit_from_vcvars) 2 | # vcvarsall.bat does not check if the PATH is already populated 3 | # with the necessary directories, and just naively appends them 4 | # to the existing PATH. Because of this, if this function gets 5 | # called more than a few times, it extends the PATH to exceed 6 | # a valid length and fails. Here I'm checking if the DevEnvDir 7 | # environment variable is set, and shortcutting this function 8 | # if so. 9 | # CMake will call the toolchain file multiple times when configuring 10 | # for the first time to do things like check if a compiler works 11 | # for a super simple file. Even though we check if we've set variables 12 | # in the cache to not re-call this function, the cache is NOT 13 | # populated until AFTER all the initial configuration is done, 14 | # therefore we must make sure that it's not a subsequent call from 15 | # checking the actual CMD environment 16 | execute_process(COMMAND cmd /c if defined DevEnvDir (exit 1) RESULT_VARIABLE CMD_RESULT) 17 | if(NOT CMD_RESULT EQUAL 0) 18 | return() 19 | endif() 20 | 21 | # Adapted from Modules/Platform/Windows-GNU.cmake 22 | set(VS_INSTALLER_PATHS "") 23 | set(VS_VERSIONS 17) 24 | foreach(VER ${VS_VERSIONS}) # change the first number to the largest supported version 25 | cmake_host_system_information(RESULT VS_DIR QUERY VS_${VER}_DIR) 26 | if(VS_DIR) 27 | list(APPEND VS_INSTALLER_PATHS "${VS_DIR}/VC/Auxiliary/Build") 28 | endif() 29 | endforeach() 30 | 31 | find_program(VCVARSALL_PATH NAMES vcvars64.bat vcvarsamd64.bat 32 | DOC "Visual Studio vcvarsamd64.bat" 33 | PATHS ${VS_INSTALLER_PATHS} 34 | ) 35 | 36 | if(NOT EXISTS "${VCVARSALL_PATH}") 37 | message(FATAL_ERROR "Unknown VS version specified/no vcvarsall.bat detected") 38 | else() 39 | message("vcvars file at ${VCVARSALL_PATH}") 40 | endif() 41 | execute_process(COMMAND cmd /c echo {SET0} && set && echo {/SET0} && "${VCVARSALL_PATH}" x64 && echo {SET1} && set && echo {/SET1} OUTPUT_VARIABLE CMD_OUTPUT RESULT_VARIABLE CMD_RESULT) 42 | if(NOT CMD_RESULT EQUAL 0) 43 | message(FATAL_ERROR "command returned '${CMD_RESULT}'") 44 | endif() 45 | 46 | # Parse the command output 47 | set(REST "${CMD_OUTPUT}") 48 | string(FIND "${REST}" "{SET0}" BEG) 49 | string(SUBSTRING "${REST}" ${BEG} -1 REST) 50 | string(FIND "${REST}" "{/SET0}" END) 51 | string(SUBSTRING "${REST}" 0 ${END} SET0) 52 | string(SUBSTRING "${SET0}" 6 -1 SET0) 53 | string(FIND "${REST}" "{SET1}" BEG) 54 | string(SUBSTRING "${REST}" ${BEG} -1 REST) 55 | string(FIND "${REST}" "{/SET1}" END) 56 | string(SUBSTRING "${REST}" 0 ${END} SET1) 57 | string(SUBSTRING "${SET1}" 6 -1 SET1) 58 | string(REGEX MATCHALL "\n[0-9a-zA-Z_]*" SET0_VARS "${SET0}") 59 | list(TRANSFORM SET0_VARS STRIP) 60 | string(REGEX MATCHALL "\n[0-9a-zA-Z_]*" SET1_VARS "${SET1}") 61 | list(TRANSFORM SET1_VARS STRIP) 62 | 63 | function(_extract_from_set_command INPUT VARNAME OUTVAR_NAME) 64 | set(R "${INPUT}") 65 | string(FIND "${R}" "\n${VARNAME}=" B) 66 | if(B EQUAL -1) 67 | set(${OUTVAR_NAME} "" PARENT_SCOPE) 68 | return() 69 | endif() 70 | string(SUBSTRING "${R}" ${B} -1 R) 71 | string(SUBSTRING "${R}" 1 -1 R) 72 | string(FIND "${R}" "\n" E) 73 | string(SUBSTRING "${R}" 0 ${E} OUT_TEMP) 74 | string(LENGTH "${VARNAME}=" VARNAME_LEN) 75 | string(SUBSTRING "${OUT_TEMP}" ${VARNAME_LEN} -1 OUT_TEMP) 76 | set(${OUTVAR_NAME} "${OUT_TEMP}" PARENT_SCOPE) 77 | endfunction() 78 | set(CHANGED_VARS) 79 | # Run over all the vars in set1 (the set containing all the new environment vars) 80 | # and compare their values to their respective value in set0 (the value before 81 | # running vcvarsall) 82 | foreach(V ${SET1_VARS}) 83 | _extract_from_set_command("${SET0}" ${V} V0) 84 | _extract_from_set_command("${SET1}" ${V} V1) 85 | if(NOT ("${V0}" STREQUAL "${V1}")) 86 | # if it is different, then we'll add it to the list 87 | list(APPEND CHANGED_VARS ${V}) 88 | # and also we'll cache it as the value from set1. 89 | if(V STREQUAL "Path") 90 | string(REGEX REPLACE "\\\\" "/" V1 "${V1}") 91 | endif() 92 | set(MSVC_ENV_${V} "${V1}" CACHE STRING "") 93 | endif() 94 | endforeach() 95 | set(MSVC_ENV_VAR_NAMES ${CHANGED_VARS} CACHE STRING "") 96 | endfunction() 97 | 98 | if(NOT DEFINED MSVC_ENV_VAR_NAMES) 99 | msvc_inherit_from_vcvars() 100 | endif() 101 | -------------------------------------------------------------------------------- /src/adapters/parse/gvox_octree.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // #include 3 | #include "../shared/gvox_octree.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct OctreeParseUserState { 14 | size_t offset{}; 15 | Octree gvox_octree{}; 16 | }; 17 | 18 | // Base 19 | extern "C" void gvox_parse_adapter_gvox_octree_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 20 | auto *user_state_ptr = malloc(sizeof(OctreeParseUserState)); 21 | new (user_state_ptr) OctreeParseUserState(); 22 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 23 | } 24 | 25 | extern "C" void gvox_parse_adapter_gvox_octree_destroy(GvoxAdapterContext *ctx) { 26 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 27 | user_state.~OctreeParseUserState(); 28 | free(&user_state); 29 | } 30 | 31 | extern "C" void gvox_parse_adapter_gvox_octree_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 32 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 33 | 34 | uint64_t magic = 0; 35 | gvox_input_read(blit_ctx, user_state.offset, sizeof(magic), &magic); 36 | user_state.offset += sizeof(magic); 37 | 38 | if (magic != std::bit_cast(std::array{'g', 'v', 'o', 'c', 't', 'r', 'e', 'e'})) { 39 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "parsing a RLE format must begin with a valid magic number"); 40 | return; 41 | } 42 | 43 | gvox_input_read(blit_ctx, user_state.offset, sizeof(GvoxRegionRange), &user_state.gvox_octree.range); 44 | user_state.offset += sizeof(GvoxRegionRange); 45 | 46 | uint32_t node_n = 0; 47 | gvox_input_read(blit_ctx, user_state.offset, sizeof(node_n), &node_n); 48 | user_state.offset += sizeof(node_n); 49 | 50 | user_state.gvox_octree.nodes.resize(node_n); 51 | gvox_input_read(blit_ctx, user_state.offset, sizeof(user_state.gvox_octree.nodes[0]) * node_n, user_state.gvox_octree.nodes.data()); 52 | } 53 | 54 | extern "C" void gvox_parse_adapter_gvox_octree_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 55 | } 56 | 57 | // General 58 | extern "C" auto gvox_parse_adapter_gvox_octree_query_details() -> GvoxParseAdapterDetails { 59 | return { 60 | .preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE, 61 | }; 62 | } 63 | 64 | extern "C" auto gvox_parse_adapter_gvox_octree_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange { 65 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 66 | return user_state.gvox_octree.range; 67 | } 68 | 69 | extern "C" auto gvox_parse_adapter_gvox_octree_sample_region(GvoxBlitContext * /*blit_ctx*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t /*channel_id*/) -> GvoxSample { 70 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 71 | if (user_state.gvox_octree.nodes.size() == 1) { 72 | return {user_state.gvox_octree.nodes[0].leaf.color, 1u}; 73 | } 74 | auto x_pos = static_cast(offset->x - user_state.gvox_octree.range.offset.x); 75 | auto y_pos = static_cast(offset->y - user_state.gvox_octree.range.offset.y); 76 | auto z_pos = static_cast(offset->z - user_state.gvox_octree.range.offset.z); 77 | uint32_t const voxel_data = user_state.gvox_octree.sample(user_state.gvox_octree.nodes[0].parent, x_pos, y_pos, z_pos); 78 | return {voxel_data, 1u}; 79 | } 80 | 81 | // Serialize Driven 82 | extern "C" auto gvox_parse_adapter_gvox_octree_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t { 83 | return 0; 84 | } 85 | 86 | extern "C" auto gvox_parse_adapter_gvox_octree_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 87 | // auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 88 | if ((channel_flags & ~static_cast(GVOX_CHANNEL_BIT_COLOR)) != 0u) { 89 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 90 | } 91 | GvoxRegion const region = { 92 | .range = *range, 93 | .channels = channel_flags & GVOX_CHANNEL_BIT_COLOR, 94 | .flags = 0u, 95 | .data = nullptr, 96 | }; 97 | return region; 98 | } 99 | 100 | extern "C" void gvox_parse_adapter_gvox_octree_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 101 | } 102 | 103 | // Parse Driven 104 | extern "C" void gvox_parse_adapter_gvox_octree_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 105 | // auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 106 | if ((channel_flags & ~static_cast(GVOX_CHANNEL_BIT_COLOR)) != 0u) { 107 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 108 | } 109 | GvoxRegion const region = { 110 | .range = *range, 111 | .channels = channel_flags & GVOX_CHANNEL_BIT_COLOR, 112 | .flags = 0u, 113 | .data = nullptr, 114 | }; 115 | gvox_emit_region(blit_ctx, ®ion); 116 | } 117 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | include(cmake/warnings.cmake) 4 | 5 | if(GVOX_ENABLE_FILE_IO) 6 | list(APPEND VCPKG_MANIFEST_FEATURES "file-io") 7 | endif() 8 | if(GVOX_ENABLE_TESTS) 9 | list(APPEND VCPKG_MANIFEST_FEATURES "tests") 10 | endif() 11 | 12 | project(gvox VERSION 1.3.0) 13 | 14 | if(GVOX_ENABLE_STATIC_ANALYSIS) 15 | set(CPPCHECK_TEMPLATE "gcc") 16 | find_program(CPPCHECK cppcheck) 17 | find_program(CLANG_TIDY clang-tidy) 18 | if(CPPCHECK) 19 | set(CMAKE_CXX_CPPCHECK 20 | ${CPPCHECK} 21 | --template=${CPPCHECK_TEMPLATE} 22 | --enable=style,performance,warning,portability 23 | --inline-suppr 24 | --suppress=cppcheckError 25 | --suppress=internalAstError 26 | --suppress=unmatchedSuppression 27 | --suppress=preprocessorErrorDirective 28 | --suppress=exceptThrowInDestructor 29 | --suppress=functionStatic 30 | --inconclusive) 31 | endif() 32 | if(CLANG_TIDY) 33 | set(CMAKE_CXX_CLANG_TIDY 34 | ${CLANG_TIDY} 35 | --fix) 36 | endif() 37 | endif() 38 | 39 | set(GVOX_INPUT_ADAPTERS 40 | "byte_buffer" 41 | ) 42 | set(GVOX_OUTPUT_ADAPTERS 43 | "byte_buffer" 44 | ) 45 | set(GVOX_PARSE_ADAPTERS 46 | "gvox_raw" 47 | "gvox_palette" 48 | "gvox_run_length_encoding" 49 | "gvox_octree" 50 | "gvox_global_palette" 51 | "gvox_brickmap" 52 | "voxlap" 53 | "kvx" 54 | "magicavoxel" 55 | ) 56 | set(GVOX_SERIALIZE_ADAPTERS 57 | "gvox_raw" 58 | "gvox_palette" 59 | "gvox_run_length_encoding" 60 | "gvox_octree" 61 | "gvox_global_palette" 62 | "gvox_brickmap" 63 | "colored_text" 64 | "random_sample" 65 | ) 66 | 67 | if(GVOX_BUILD_FOR_JAVA) 68 | set(BUILD_SHARED_LIBS ON) 69 | endif() 70 | 71 | add_library(${PROJECT_NAME} 72 | "src/gvox.cpp" 73 | ) 74 | 75 | if(GVOX_ENABLE_THREADSAFETY) 76 | target_compile_definitions(${PROJECT_NAME} PUBLIC GVOX_ENABLE_THREADSAFETY=1) 77 | else() 78 | target_compile_definitions(${PROJECT_NAME} PUBLIC GVOX_ENABLE_THREADSAFETY=0) 79 | endif() 80 | 81 | if(GVOX_BUILD_FOR_WEB) 82 | set(GVOX_ENABLE_MULTITHREADED_ADAPTERS false) 83 | else() 84 | find_package(Threads REQUIRED) 85 | target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads) 86 | endif() 87 | 88 | if(GVOX_BUILD_FOR_RUST) 89 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_BUILD_FOR_RUST=1) 90 | else() 91 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_BUILD_FOR_RUST=0) 92 | endif() 93 | 94 | if(GVOX_BUILD_FOR_ODIN) 95 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_BUILD_FOR_ODIN=1) 96 | else() 97 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_BUILD_FOR_ODIN=0) 98 | endif() 99 | 100 | if(GVOX_BUILD_FOR_JAVA) 101 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_BUILD_FOR_JAVA=1) 102 | else() 103 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_BUILD_FOR_JAVA=0) 104 | endif() 105 | 106 | if(GVOX_ENABLE_MULTITHREADED_ADAPTERS) 107 | target_compile_definitions(${PROJECT_NAME} PUBLIC GVOX_ENABLE_MULTITHREADED_ADAPTERS=1) 108 | else() 109 | target_compile_definitions(${PROJECT_NAME} PUBLIC GVOX_ENABLE_MULTITHREADED_ADAPTERS=0) 110 | endif() 111 | 112 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}) 113 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_VERSION_MINOR=${PROJECT_VERSION_MINOR}) 114 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_VERSION_PATCH=${PROJECT_VERSION_PATCH}) 115 | 116 | if(BUILD_SHARED_LIBS) 117 | if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 118 | target_compile_definitions(${PROJECT_NAME} 119 | PRIVATE "GVOX_EXPORT=__declspec(dllexport)" 120 | INTERFACE "GVOX_EXPORT=__declspec(dllimport)") 121 | else() 122 | target_compile_definitions(${PROJECT_NAME} 123 | PUBLIC "GVOX_EXPORT=") 124 | endif() 125 | else() 126 | target_compile_definitions(${PROJECT_NAME} 127 | PUBLIC "GVOX_EXPORT=") 128 | endif() 129 | 130 | if((GVOX_BUILD_FOR_RUST OR GVOX_BUILD_FOR_ODIN) AND GVOX_USE_STATIC_CRT) 131 | if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 132 | set_property(TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 133 | endif() 134 | endif() 135 | 136 | if(GVOX_ENABLE_FILE_IO) 137 | target_compile_definitions(${PROJECT_NAME} PUBLIC GVOX_ENABLE_FILE_IO=1) 138 | list(APPEND GVOX_INPUT_ADAPTERS "file") 139 | list(APPEND GVOX_OUTPUT_ADAPTERS "file" "stdout") 140 | else() 141 | target_compile_definitions(${PROJECT_NAME} PUBLIC GVOX_ENABLE_FILE_IO=0) 142 | endif() 143 | 144 | add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 145 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) 146 | include(GNUInstallDirs) 147 | target_include_directories(${PROJECT_NAME} 148 | PUBLIC 149 | $ 150 | $ 151 | $ 152 | ) 153 | set_project_warnings(${PROJECT_NAME}) 154 | 155 | include("cmake/adapters.cmake") 156 | 157 | function(set_project_sanitizers TARGET_NAME) 158 | set(SANITIZER_FLAGS 159 | # $<$:/fsanitize=address> 160 | $<$,$>>:-fsanitize=address> 161 | ) 162 | if(GVOX_ENABLE_ASAN) 163 | target_compile_options(${TARGET_NAME} PRIVATE ${SANITIZER_FLAGS}) 164 | target_link_options(${TARGET_NAME} PRIVATE ${SANITIZER_FLAGS}) 165 | endif() 166 | endfunction() 167 | 168 | set_project_sanitizers(${PROJECT_NAME}) 169 | 170 | if(GVOX_ENABLE_TESTS) 171 | add_subdirectory(tests) 172 | endif() 173 | 174 | # Packaging 175 | if(NOT GVOX_DISABLE_PACKAGING) 176 | include(cmake/packaging.cmake) 177 | endif() 178 | -------------------------------------------------------------------------------- /src/adapters/serialize/gvox_raw.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | struct GvoxRawUserState { 11 | GvoxRegionRange range{}; 12 | std::vector voxels; 13 | std::vector channels; 14 | size_t offset{}; 15 | }; 16 | 17 | // Base 18 | extern "C" void gvox_serialize_adapter_gvox_raw_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 19 | auto *user_state_ptr = malloc(sizeof(GvoxRawUserState)); 20 | new (user_state_ptr) GvoxRawUserState(); 21 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 22 | } 23 | 24 | extern "C" void gvox_serialize_adapter_gvox_raw_destroy(GvoxAdapterContext *ctx) { 25 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 26 | user_state.~GvoxRawUserState(); 27 | free(&user_state); 28 | } 29 | 30 | extern "C" void gvox_serialize_adapter_gvox_raw_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 31 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 32 | user_state.offset = 0; 33 | user_state.range = *range; 34 | auto magic = std::bit_cast(std::array{'g', 'v', 'r', '\0'}); 35 | gvox_output_write(blit_ctx, user_state.offset, sizeof(uint32_t), &magic); 36 | user_state.offset += sizeof(magic); 37 | gvox_output_write(blit_ctx, user_state.offset, sizeof(*range), range); 38 | user_state.offset += sizeof(*range); 39 | gvox_output_write(blit_ctx, user_state.offset, sizeof(channel_flags), &channel_flags); 40 | user_state.offset += sizeof(channel_flags); 41 | user_state.channels.resize(static_cast(std::popcount(channel_flags))); 42 | uint32_t next_channel = 0; 43 | for (uint8_t channel_i = 0; channel_i < 32; ++channel_i) { 44 | if ((channel_flags & (1u << channel_i)) != 0) { 45 | user_state.channels[next_channel] = channel_i; 46 | ++next_channel; 47 | } 48 | } 49 | user_state.voxels.resize(user_state.channels.size() * range->extent.x * range->extent.y * range->extent.z); 50 | } 51 | 52 | extern "C" void gvox_serialize_adapter_gvox_raw_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx) { 53 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 54 | gvox_output_write(blit_ctx, user_state.offset, user_state.voxels.size() * sizeof(user_state.voxels[0]), user_state.voxels.data()); 55 | } 56 | 57 | static void handle_region(GvoxRawUserState &user_state, GvoxRegionRange const *range, auto user_func) { 58 | for (uint32_t zi = 0; zi < range->extent.z; ++zi) { 59 | for (uint32_t yi = 0; yi < range->extent.y; ++yi) { 60 | for (uint32_t xi = 0; xi < range->extent.x; ++xi) { 61 | auto const pos = GvoxOffset3D{ 62 | static_cast(xi) + range->offset.x, 63 | static_cast(yi) + range->offset.y, 64 | static_cast(zi) + range->offset.z, 65 | }; 66 | if (pos.x < user_state.range.offset.x || 67 | pos.y < user_state.range.offset.y || 68 | pos.z < user_state.range.offset.z || 69 | pos.x >= user_state.range.offset.x + static_cast(user_state.range.extent.x) || 70 | pos.y >= user_state.range.offset.y + static_cast(user_state.range.extent.y) || 71 | pos.z >= user_state.range.offset.z + static_cast(user_state.range.extent.z)) { 72 | continue; 73 | } 74 | auto output_rel_x = static_cast(pos.x - user_state.range.offset.x); 75 | auto output_rel_y = static_cast(pos.y - user_state.range.offset.y); 76 | auto output_rel_z = static_cast(pos.z - user_state.range.offset.z); 77 | for (uint32_t channel_i = 0; channel_i < user_state.channels.size(); ++channel_i) { 78 | auto output_index = static_cast(output_rel_x + output_rel_y * user_state.range.extent.x + output_rel_z * user_state.range.extent.x * user_state.range.extent.y) * user_state.channels.size() + channel_i; 79 | user_func(channel_i, output_index, pos); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | // Serialize Driven 87 | extern "C" void gvox_serialize_adapter_gvox_raw_serialize_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t /* channel_flags */) { 88 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 89 | handle_region( 90 | user_state, range, 91 | [blit_ctx, &user_state](uint32_t channel_i, size_t output_index, GvoxOffset3D const &pos) { 92 | auto const sample_range = GvoxRegionRange{ 93 | .offset = pos, 94 | .extent = GvoxExtent3D{1, 1, 1}, 95 | }; 96 | auto region = gvox_load_region_range(blit_ctx, &sample_range, 1u << user_state.channels[channel_i]); 97 | auto sample = gvox_sample_region(blit_ctx, ®ion, &pos, user_state.channels[channel_i]); 98 | if (sample.is_present == 0u) { 99 | sample.data = 0u; 100 | } 101 | user_state.voxels[output_index] = sample.data; 102 | gvox_unload_region_range(blit_ctx, ®ion, &sample_range); 103 | }); 104 | } 105 | 106 | // Parse Driven 107 | extern "C" void gvox_serialize_adapter_gvox_raw_receive_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region) { 108 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 109 | handle_region( 110 | user_state, ®ion->range, 111 | [blit_ctx, region, &user_state](uint32_t channel_i, size_t output_index, GvoxOffset3D const &pos) { 112 | auto sample = gvox_sample_region(blit_ctx, region, &pos, user_state.channels[channel_i]); 113 | if (sample.is_present != 0u) { 114 | user_state.voxels[output_index] = sample.data; 115 | } 116 | }); 117 | } 118 | -------------------------------------------------------------------------------- /src/adapters/parse/gvox_raw.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct GvoxRawParseUserState { 13 | GvoxRegionRange range{}; 14 | uint32_t channel_flags{}; 15 | uint32_t channel_n{}; 16 | size_t offset{}; 17 | 18 | // pre-load? 19 | std::vector voxels{}; 20 | }; 21 | 22 | // Base 23 | extern "C" void gvox_parse_adapter_gvox_raw_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 24 | auto *user_state_ptr = malloc(sizeof(GvoxRawParseUserState)); 25 | new (user_state_ptr) GvoxRawParseUserState(); 26 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 27 | } 28 | 29 | extern "C" void gvox_parse_adapter_gvox_raw_destroy(GvoxAdapterContext *ctx) { 30 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 31 | user_state.~GvoxRawParseUserState(); 32 | free(&user_state); 33 | } 34 | 35 | extern "C" void gvox_parse_adapter_gvox_raw_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 36 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 37 | 38 | uint32_t magic = 0; 39 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &magic); 40 | user_state.offset += sizeof(uint32_t); 41 | 42 | if (magic != std::bit_cast(std::array{'g', 'v', 'r', '\0'})) { 43 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "parsing a gvox raw format must begin with a valid magic number"); 44 | return; 45 | } 46 | 47 | gvox_input_read(blit_ctx, user_state.offset, sizeof(GvoxRegionRange), &user_state.range); 48 | user_state.offset += sizeof(GvoxRegionRange); 49 | 50 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &user_state.channel_flags); 51 | user_state.offset += sizeof(uint32_t); 52 | 53 | user_state.channel_n = static_cast(std::popcount(user_state.channel_flags)); 54 | 55 | user_state.voxels.resize(static_cast(user_state.range.extent.x) * user_state.range.extent.y * user_state.range.extent.z * user_state.channel_n); 56 | gvox_input_read(blit_ctx, user_state.offset, user_state.voxels.size() * sizeof(user_state.voxels[0]), user_state.voxels.data()); 57 | } 58 | 59 | extern "C" void gvox_parse_adapter_gvox_raw_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 60 | } 61 | 62 | // General 63 | extern "C" auto gvox_parse_adapter_gvox_raw_query_details() -> GvoxParseAdapterDetails { 64 | return { 65 | .preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE, 66 | }; 67 | } 68 | 69 | extern "C" auto gvox_parse_adapter_gvox_raw_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange { 70 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 71 | return user_state.range; 72 | } 73 | 74 | extern "C" auto gvox_parse_adapter_gvox_raw_sample_region(GvoxBlitContext * /*blit_ctx*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample { 75 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 76 | uint32_t voxel_data = 0; 77 | uint32_t voxel_channel_index = 0; 78 | for (uint32_t channel_index = 0; channel_index < channel_id; ++channel_index) { 79 | if (((user_state.channel_flags >> channel_index) & 0x1) != 0) { 80 | ++voxel_channel_index; 81 | } 82 | } 83 | auto voxel_index = voxel_channel_index + user_state.channel_n * (static_cast(offset->x - user_state.range.offset.x) + static_cast(offset->y - user_state.range.offset.y) * user_state.range.extent.x + static_cast(offset->z - user_state.range.offset.z) * user_state.range.extent.x * user_state.range.extent.y); 84 | // auto base_offset = user_state.offset; 85 | // auto read_offset = base_offset + sizeof(uint32_t) * voxel_index; 86 | // gvox_input_read(blit_ctx, read_offset, sizeof(voxel_data), &voxel_data); 87 | voxel_data = user_state.voxels[voxel_index]; 88 | return {voxel_data, 1u}; 89 | } 90 | 91 | // Serialize Driven 92 | extern "C" auto gvox_parse_adapter_gvox_raw_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t { 93 | return 0; 94 | } 95 | 96 | extern "C" auto gvox_parse_adapter_gvox_raw_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 97 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 98 | if ((channel_flags & ~user_state.channel_flags) != 0) { 99 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 100 | } 101 | GvoxRegion const region = { 102 | .range = *range, 103 | .channels = channel_flags & user_state.channel_flags, 104 | .flags = 0u, 105 | .data = nullptr, 106 | }; 107 | return region; 108 | } 109 | 110 | extern "C" void gvox_parse_adapter_gvox_raw_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 111 | } 112 | 113 | // Parse Driven 114 | extern "C" void gvox_parse_adapter_gvox_raw_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 115 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 116 | if ((channel_flags & ~user_state.channel_flags) != 0) { 117 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 118 | } 119 | GvoxRegion const region = { 120 | .range = *range, 121 | .channels = channel_flags & user_state.channel_flags, 122 | .flags = 0u, 123 | .data = nullptr, 124 | }; 125 | gvox_emit_region(blit_ctx, ®ion); 126 | } 127 | -------------------------------------------------------------------------------- /tests/simple/parsing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // Called when creating the adapter context 10 | void create(GvoxAdapterContext *ctx, void const *config) { 11 | // here you can create any resources you want to associate with your adapter. 12 | // you can tie your state to the adapter context `ctx` with this function: 13 | // void *my_pointer = malloc(sizeof(int)); 14 | // gvox_adapter_set_user_pointer(ctx, my_pointer); 15 | // which can be retrieved at any time with the get variant of this function. 16 | } 17 | // Called when destroying the adapter context (for freeing any resources created by the adapter) 18 | void destroy(GvoxAdapterContext *ctx) { 19 | // here we'd free `my_pointer` 20 | // void *my_pointer = gvox_adapter_get_user_pointer(ctx); 21 | // free(my_pointer); 22 | } 23 | // Called once, at the beginning of a blit operation. 24 | void blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 25 | // We get a minimal description of the volume we're blitting. 26 | } 27 | // Called once, at the end of a blit operation. 28 | void blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx) { 29 | } 30 | // Legacy... We don't care to implement. 31 | void serialize_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 32 | } 33 | 34 | // This function may be called in a parallel nature by the parse adapter. 35 | void receive_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region) { 36 | // `GvoxRegion` description: 37 | // `.range.offset` is the 3D location of the 3D array in world-space 38 | // `.range.extent` is the dimensions of the 3D array. 39 | // `.channels` is the channel flags, in this case it should just be `GVOX_CHANNEL_BIT_COLOR`. 40 | // `.flags` is a set of GVOX_REGION_FLAG_ bits, which describe extra metadata about the region. 41 | 42 | GvoxOffset3D sample_position = region->range.offset; 43 | // In order to sample voxel data from the region, use `gvox_sample_region()`: 44 | // - blit_ctx is necessary as the data is extracted from the parser's custom region data format. 45 | // - region is the pointer to the region that has been acquired. 46 | // - sample position is the coordinate from [ range.offset, range.offset + range.extent ) that the serializer 47 | // would like to query. If a coordinate is specified outside this range, the resulting sample should have 48 | // 0 for `.is_present`. 49 | GvoxSample region_sample = gvox_sample_region(blit_ctx, region, &sample_position, GVOX_CHANNEL_ID_COLOR); 50 | // `GvoxSample` description: 51 | // `.is_present` is either 0 or 1 depending on whether there is valid data at the specified coordinate. 52 | // `.data` is a single uint32_t that holds the actual data. How this data is represented is defined by 53 | // the channel in question. If, for example, one requested GVOX_CHANNEL_ID_COLOR, the 8bpc color data 54 | // would be packed into the first 24 bits of the uint32_t. 55 | 56 | { 57 | static auto printf_mtx = std::mutex{}; 58 | 59 | // If necessary in our application, we may need to synchronize. For example, 60 | // here we need to lock printf because we only want one thread writing to 61 | // the console at any time. 62 | auto lock = std::lock_guard{printf_mtx}; 63 | 64 | uint8_t r = 0; 65 | uint8_t g = 0; 66 | uint8_t b = 0; 67 | if (region_sample.is_present != 0) { 68 | r = (region_sample.data >> 0u) & 0xff; 69 | g = (region_sample.data >> 8u) & 0xff; 70 | b = (region_sample.data >> 16u) & 0xff; 71 | } 72 | // print-out the color of the voxel in the 0, 0, 0 corner of the region 73 | printf("\033[48;2;%03d;%03d;%03dm \033[0m", r, g, b); 74 | printf("offset: (%d %d %d) extent: (%d %d %d) channels: %d flags: %d\n", 75 | region->range.offset.x, 76 | region->range.offset.y, 77 | region->range.offset.z, 78 | region->range.extent.x, 79 | region->range.extent.y, 80 | region->range.extent.z, 81 | region->channels, 82 | region->flags); 83 | } 84 | } 85 | 86 | void handle_gvox_error(GvoxContext *gvox_ctx) { 87 | GvoxResult res = gvox_get_result(gvox_ctx); 88 | int error_count = 0; 89 | while (res != GVOX_RESULT_SUCCESS) { 90 | size_t size = 0; 91 | gvox_get_result_message(gvox_ctx, nullptr, &size); 92 | char *str = new char[size + 1]; 93 | gvox_get_result_message(gvox_ctx, str, nullptr); 94 | str[size] = '\0'; 95 | printf("ERROR: %s\n", str); 96 | gvox_pop_result(gvox_ctx); 97 | delete[] str; 98 | res = gvox_get_result(gvox_ctx); 99 | ++error_count; 100 | } 101 | if (error_count != 0) { 102 | exit(-error_count); 103 | } 104 | } 105 | 106 | auto const my_adapter_info = GvoxSerializeAdapterInfo{ 107 | .base_info = { 108 | .name_str = "my_adapter", 109 | .create = create, 110 | .destroy = destroy, 111 | .blit_begin = blit_begin, 112 | .blit_end = blit_end, 113 | }, 114 | .serialize_region = serialize_region, 115 | .receive_region = receive_region, 116 | }; 117 | 118 | auto main() -> int { 119 | auto *gvox_ctx = gvox_create_context(); 120 | 121 | // register our custom adapter that'll receive all the model data 122 | gvox_register_serialize_adapter(gvox_ctx, &my_adapter_info); 123 | 124 | auto const *model_filepath = "tests/simple/nuke.vox"; 125 | 126 | auto i_config = GvoxFileInputAdapterConfig{.filepath = model_filepath, .byte_offset = 0}; 127 | auto *i_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_input_adapter(gvox_ctx, "file"), &i_config); 128 | auto *p_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_parse_adapter(gvox_ctx, "magicavoxel"), nullptr); 129 | // we don't need an output adapter, as we're not using it in our serialize adapter. 130 | auto *o_ctx = (GvoxAdapterContext *)nullptr; 131 | auto *s_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_serialize_adapter(gvox_ctx, "my_adapter"), nullptr); 132 | 133 | #if 0 // we optionally can provide a specified range of the file to parse 134 | auto region_range = GvoxRegionRange{.offset = {-4, -4, -4}, .extent = {8, 8, 8}}; 135 | auto *region_range_ptr = ®ion_range; 136 | #else // otherwise, we're going to parse the entire region of the file 137 | auto *region_range_ptr = (GvoxRegionRange *)nullptr; 138 | #endif 139 | 140 | // lets parse only color data 141 | gvox_blit_region_parse_driven(i_ctx, o_ctx, p_ctx, s_ctx, region_range_ptr, GVOX_CHANNEL_BIT_COLOR); 142 | gvox_destroy_adapter_context(i_ctx); 143 | gvox_destroy_adapter_context(p_ctx); 144 | gvox_destroy_adapter_context(s_ctx); 145 | 146 | handle_gvox_error(gvox_ctx); 147 | 148 | gvox_destroy_context(gvox_ctx); 149 | } 150 | -------------------------------------------------------------------------------- /tests/simple/new_formats.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | void handle_gvox_error(GvoxContext *gvox_ctx) { 19 | GvoxResult res = gvox_get_result(gvox_ctx); 20 | int error_count = 0; 21 | while (res != GVOX_RESULT_SUCCESS) { 22 | size_t size = 0; 23 | gvox_get_result_message(gvox_ctx, nullptr, &size); 24 | char *str = new char[size + 1]; 25 | gvox_get_result_message(gvox_ctx, str, nullptr); 26 | str[size] = '\0'; 27 | printf("ERROR: %s\n", str); 28 | gvox_pop_result(gvox_ctx); 29 | delete[] str; 30 | res = gvox_get_result(gvox_ctx); 31 | ++error_count; 32 | } 33 | if (error_count != 0) { 34 | exit(-error_count); 35 | } 36 | } 37 | 38 | auto const procedural_adapter_info = GvoxParseAdapterInfo{ 39 | .base_info = { 40 | .name_str = "procedural", 41 | .create = procedural_create, 42 | .destroy = procedural_destroy, 43 | .blit_begin = procedural_blit_begin, 44 | .blit_end = procedural_blit_end, 45 | }, 46 | .query_details = procedural_query_details, 47 | .sample_region = procedural_sample_region, 48 | .query_region_flags = procedural_query_region_flags, 49 | .load_region = procedural_load_region, 50 | .unload_region = procedural_unload_region, 51 | .parse_region = procedural_parse_region, 52 | }; 53 | 54 | #define PARSE_MAGICAVOXEL 0 55 | #define MAGICAVOXEL_FILE_NAME "mansion" 56 | #define TEST_SERIALIZATION 1 57 | #define TEST_RANDOM_ACCESS_SPEED 1 58 | 59 | void test_adapter(char const *const output_filepath, char const *const adapter_name) { 60 | auto *gvox_ctx = gvox_create_context(); 61 | #if PARSE_MAGICAVOXEL 62 | GvoxRegionRange *region_range_ptr = nullptr; 63 | #else 64 | auto region_range = GvoxRegionRange{.offset = {-4, -4, -4}, .extent = {8, 8, 8}}; 65 | auto *region_range_ptr = ®ion_range; 66 | #endif 67 | 68 | #if TEST_SERIALIZATION 69 | // Create file 70 | { 71 | auto i_config = GvoxFileInputAdapterConfig{.filepath = "tests/simple/" MAGICAVOXEL_FILE_NAME ".vox", .byte_offset = 0}; 72 | auto o_config = GvoxFileOutputAdapterConfig{.filepath = output_filepath}; 73 | auto *i_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_input_adapter(gvox_ctx, "file"), &i_config); 74 | #if PARSE_MAGICAVOXEL 75 | auto *p_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_parse_adapter(gvox_ctx, "magicavoxel"), NULL); 76 | #else 77 | auto *p_ctx = gvox_create_adapter_context(gvox_ctx, gvox_register_parse_adapter(gvox_ctx, &procedural_adapter_info), nullptr); 78 | #endif 79 | auto *o_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_output_adapter(gvox_ctx, "file"), &o_config); 80 | auto *s_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_serialize_adapter(gvox_ctx, adapter_name), nullptr); 81 | gvox_blit_region(i_ctx, o_ctx, p_ctx, s_ctx, region_range_ptr, GVOX_CHANNEL_BIT_COLOR); 82 | gvox_destroy_adapter_context(i_ctx); 83 | gvox_destroy_adapter_context(o_ctx); 84 | gvox_destroy_adapter_context(p_ctx); 85 | gvox_destroy_adapter_context(s_ctx); 86 | } 87 | handle_gvox_error(gvox_ctx); 88 | #endif 89 | 90 | #if !PARSE_MAGICAVOXEL 91 | // Load file 92 | { 93 | auto i_config = GvoxFileInputAdapterConfig{.filepath = output_filepath, .byte_offset = 0}; 94 | auto s_config = GvoxColoredTextSerializeAdapterConfig{.non_color_max_value = 5}; 95 | auto *i_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_input_adapter(gvox_ctx, "file"), &i_config); 96 | auto *o_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_output_adapter(gvox_ctx, "stdout"), nullptr); 97 | auto *p_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_parse_adapter(gvox_ctx, adapter_name), nullptr); 98 | auto *s_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_serialize_adapter(gvox_ctx, "colored_text"), &s_config); 99 | gvox_blit_region(i_ctx, o_ctx, p_ctx, s_ctx, region_range_ptr, GVOX_CHANNEL_BIT_COLOR); 100 | gvox_destroy_adapter_context(i_ctx); 101 | gvox_destroy_adapter_context(o_ctx); 102 | gvox_destroy_adapter_context(p_ctx); 103 | gvox_destroy_adapter_context(s_ctx); 104 | } 105 | handle_gvox_error(gvox_ctx); 106 | #endif 107 | 108 | #if TEST_RANDOM_ACCESS_SPEED 109 | // Load file 110 | { 111 | uint8_t *output_bytes = nullptr; 112 | size_t output_size = 0; 113 | auto i_config = GvoxFileInputAdapterConfig{.filepath = output_filepath, .byte_offset = 0}; 114 | auto o_config = GvoxByteBufferOutputAdapterConfig{.out_size = &output_size, .out_byte_buffer_ptr = &output_bytes}; 115 | auto s_config = GvoxRandomSampleSerializeAdapterConfig{.sample_count = 10000000}; 116 | auto *i_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_input_adapter(gvox_ctx, "file"), &i_config); 117 | auto *o_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_output_adapter(gvox_ctx, "byte_buffer"), &o_config); 118 | auto *p_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_parse_adapter(gvox_ctx, adapter_name), nullptr); 119 | auto *s_ctx = gvox_create_adapter_context(gvox_ctx, gvox_get_serialize_adapter(gvox_ctx, "random_sample"), &s_config); 120 | gvox_blit_region_serialize_driven(i_ctx, o_ctx, p_ctx, s_ctx, region_range_ptr, GVOX_CHANNEL_BIT_COLOR); 121 | gvox_destroy_adapter_context(i_ctx); 122 | gvox_destroy_adapter_context(p_ctx); 123 | gvox_destroy_adapter_context(s_ctx); 124 | auto duration = *reinterpret_cast(output_bytes); 125 | printf("%fs\n", duration); 126 | free(output_bytes); 127 | } 128 | handle_gvox_error(gvox_ctx); 129 | #endif 130 | 131 | gvox_destroy_context(gvox_ctx); 132 | } 133 | 134 | auto main() -> int { 135 | printf("raw\n"); 136 | test_adapter("tests/simple/outputs/" MAGICAVOXEL_FILE_NAME "_gvox_raw.gvr", "gvox_raw"); 137 | printf("palette\n"); 138 | test_adapter("tests/simple/outputs/" MAGICAVOXEL_FILE_NAME "_gvox_palette.gvox", "gvox_palette"); 139 | printf("run length\n"); 140 | test_adapter("tests/simple/outputs/" MAGICAVOXEL_FILE_NAME "_run_length_encoding.rle", "gvox_run_length_encoding"); 141 | printf("octree\n"); 142 | test_adapter("tests/simple/outputs/" MAGICAVOXEL_FILE_NAME "_octree.oct", "gvox_octree"); 143 | printf("global palette\n"); 144 | test_adapter("tests/simple/outputs/" MAGICAVOXEL_FILE_NAME "_global_palette.glp", "gvox_global_palette"); 145 | printf("brickmap\n"); 146 | test_adapter("tests/simple/outputs/" MAGICAVOXEL_FILE_NAME "_brickmap.brk", "gvox_brickmap"); 147 | } 148 | -------------------------------------------------------------------------------- /src/adapters/parse/gvox_brickmap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../shared/gvox_brickmap.hpp" 13 | 14 | struct BrickmapParseUserState { 15 | GvoxRegionRange range{}; 16 | uint32_t channel_flags{}; 17 | uint32_t channel_n{}; 18 | size_t offset{}; 19 | 20 | GvoxExtent3D bricks_extent{}; 21 | std::vector bricks_heap{}; 22 | std::vector brick_headers{}; 23 | }; 24 | 25 | // Base 26 | extern "C" void gvox_parse_adapter_gvox_brickmap_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 27 | auto *user_state_ptr = malloc(sizeof(BrickmapParseUserState)); 28 | new (user_state_ptr) BrickmapParseUserState(); 29 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 30 | } 31 | 32 | extern "C" void gvox_parse_adapter_gvox_brickmap_destroy(GvoxAdapterContext *ctx) { 33 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 34 | user_state.~BrickmapParseUserState(); 35 | free(&user_state); 36 | } 37 | 38 | extern "C" void gvox_parse_adapter_gvox_brickmap_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 39 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 40 | 41 | uint32_t magic = 0; 42 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &magic); 43 | user_state.offset += sizeof(uint32_t); 44 | 45 | if (magic != std::bit_cast(std::array{'b', 'r', 'k', '\0'})) { 46 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "parsing a gvox raw format must begin with a valid magic number"); 47 | return; 48 | } 49 | 50 | gvox_input_read(blit_ctx, user_state.offset, sizeof(GvoxRegionRange), &user_state.range); 51 | user_state.offset += sizeof(GvoxRegionRange); 52 | 53 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &user_state.channel_flags); 54 | user_state.offset += sizeof(uint32_t); 55 | 56 | uint32_t heap_size = 0; 57 | gvox_input_read(blit_ctx, user_state.offset, sizeof(heap_size), &heap_size); 58 | user_state.offset += sizeof(heap_size); 59 | 60 | user_state.channel_n = static_cast(std::popcount(user_state.channel_flags)); 61 | 62 | user_state.bricks_extent.x = (user_state.range.extent.x + 7) / 8; 63 | user_state.bricks_extent.y = (user_state.range.extent.y + 7) / 8; 64 | user_state.bricks_extent.z = (user_state.range.extent.z + 7) / 8; 65 | 66 | user_state.brick_headers.resize(static_cast(user_state.channel_n) * user_state.bricks_extent.x * user_state.bricks_extent.y * user_state.bricks_extent.z); 67 | user_state.bricks_heap.resize(heap_size); 68 | 69 | gvox_input_read(blit_ctx, user_state.offset, user_state.brick_headers.size() * sizeof(user_state.brick_headers[0]), user_state.brick_headers.data()); 70 | user_state.offset += user_state.brick_headers.size() * sizeof(user_state.brick_headers[0]); 71 | 72 | gvox_input_read(blit_ctx, user_state.offset, user_state.bricks_heap.size() * sizeof(user_state.bricks_heap[0]), user_state.bricks_heap.data()); 73 | user_state.offset += user_state.bricks_heap.size() * sizeof(user_state.bricks_heap[0]); 74 | } 75 | 76 | extern "C" void gvox_parse_adapter_gvox_brickmap_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 77 | } 78 | 79 | // General 80 | extern "C" auto gvox_parse_adapter_gvox_brickmap_query_details() -> GvoxParseAdapterDetails { 81 | return { 82 | .preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE, 83 | }; 84 | } 85 | 86 | extern "C" auto gvox_parse_adapter_gvox_brickmap_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange { 87 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 88 | return user_state.range; 89 | } 90 | 91 | extern "C" auto gvox_parse_adapter_gvox_brickmap_sample_region(GvoxBlitContext * /*blit_ctx*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample { 92 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 93 | uint32_t voxel_data = 0; 94 | uint32_t voxel_channel_index = 0; 95 | for (uint32_t channel_index = 0; channel_index < channel_id; ++channel_index) { 96 | if (((user_state.channel_flags >> channel_index) & 0x1) != 0) { 97 | ++voxel_channel_index; 98 | } 99 | } 100 | 101 | auto xi = static_cast(offset->x - user_state.range.offset.x); 102 | auto yi = static_cast(offset->y - user_state.range.offset.y); 103 | auto zi = static_cast(offset->z - user_state.range.offset.z); 104 | auto bxi = xi / 8; 105 | auto byi = yi / 8; 106 | auto bzi = zi / 8; 107 | 108 | auto brick_index = bxi + byi * user_state.bricks_extent.x + bzi * user_state.bricks_extent.x * user_state.bricks_extent.y; 109 | auto const &brick_header = user_state.brick_headers[brick_index * user_state.channel_n + voxel_channel_index]; 110 | 111 | if (brick_header.loaded.is_loaded) { 112 | auto sub_bxi = xi - bxi * 8; 113 | auto sub_byi = yi - byi * 8; 114 | auto sub_bzi = zi - bzi * 8; 115 | voxel_data = user_state.bricks_heap[brick_header.loaded.heap_index].voxels[sub_bxi + sub_byi * 8 + sub_bzi * 64]; 116 | } else { 117 | voxel_data = brick_header.unloaded.lod_color; 118 | } 119 | 120 | return {voxel_data, 1u}; 121 | } 122 | 123 | // Serialize Driven 124 | extern "C" auto gvox_parse_adapter_gvox_brickmap_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t { 125 | return 0; 126 | } 127 | 128 | extern "C" auto gvox_parse_adapter_gvox_brickmap_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 129 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 130 | if ((channel_flags & ~user_state.channel_flags) != 0) { 131 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 132 | } 133 | GvoxRegion const region = { 134 | .range = *range, 135 | .channels = channel_flags & user_state.channel_flags, 136 | .flags = 0u, 137 | .data = nullptr, 138 | }; 139 | return region; 140 | } 141 | 142 | extern "C" void gvox_parse_adapter_gvox_brickmap_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 143 | } 144 | 145 | // Parse Driven 146 | extern "C" void gvox_parse_adapter_gvox_brickmap_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 147 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 148 | if ((channel_flags & ~user_state.channel_flags) != 0) { 149 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 150 | } 151 | GvoxRegion const region = { 152 | .range = *range, 153 | .channels = channel_flags & user_state.channel_flags, 154 | .flags = 0u, 155 | .data = nullptr, 156 | }; 157 | gvox_emit_region(blit_ctx, ®ion); 158 | } 159 | -------------------------------------------------------------------------------- /tests/adapters/procedural.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define SIMPLE_TERRAIN 0 9 | 10 | auto stable_rand(float x) -> float { return fmod(sin(x * (91.3458f)) * 47453.5453f, 1.0f); } 11 | auto stable_rand(float x, float y) -> float { return fmod(sin(x * 12.9898f + y * 78.233f) * 43758.5453f, 1.0f); } 12 | auto stable_rand(float x, float y, float z) -> float { return stable_rand(x + stable_rand(z), y + stable_rand(z)); } 13 | auto stable_rand(int32_t xi, int32_t yi, int32_t zi) -> float { 14 | float const x = (static_cast(xi) + 0.5f) * (1.0f / 8.0f); 15 | float const y = (static_cast(yi) + 0.5f) * (1.0f / 8.0f); 16 | float const z = (static_cast(zi) + 0.5f) * (1.0f / 8.0f); 17 | return stable_rand(x, y, z); 18 | } 19 | 20 | auto fract(float x) -> float { 21 | return x - floor(x); 22 | } 23 | 24 | auto sample_terrain(float x, float y, float z) -> float { 25 | #if SIMPLE_TERRAIN 26 | return fract((x + y + z) * 0.05f) - 0.5f; 27 | #else 28 | return -(x * x + y * y + z * z) + 0.25f; 29 | #endif 30 | } 31 | 32 | auto sample_terrain_i(int32_t xi, int32_t yi, int32_t zi) -> float { 33 | float const x = (static_cast(xi) + 0.5f) * (1.0f / 8.0f); 34 | float const y = (static_cast(yi) + 0.5f) * (1.0f / 8.0f); 35 | float const z = (static_cast(zi) + 0.5f) * (1.0f / 8.0f); 36 | return sample_terrain(x, y, z); 37 | } 38 | 39 | // Base 40 | extern "C" void procedural_create(GvoxAdapterContext * /*unused*/, void const * /*unused*/) { 41 | } 42 | 43 | extern "C" void procedural_destroy(GvoxAdapterContext * /*unused*/) { 44 | } 45 | 46 | extern "C" void procedural_blit_begin(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 47 | } 48 | 49 | extern "C" void procedural_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 50 | } 51 | 52 | // General 53 | extern "C" auto procedural_query_details() -> GvoxParseAdapterDetails { 54 | return { 55 | .preferred_blit_mode = GVOX_BLIT_MODE_SERIALIZE_DRIVEN, 56 | }; 57 | } 58 | 59 | extern "C" auto procedural_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) -> GvoxRegionRange { 60 | return {{0, 0, 0}, {0, 0, 0}}; 61 | } 62 | 63 | // Serialize Driven 64 | extern "C" auto procedural_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t { 65 | return 0; 66 | } 67 | 68 | extern "C" auto procedural_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 69 | auto const available_channels = uint32_t{GVOX_CHANNEL_BIT_COLOR | GVOX_CHANNEL_BIT_NORMAL | GVOX_CHANNEL_BIT_MATERIAL_ID}; 70 | if ((channel_flags & ~available_channels) != 0) { 71 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "procedural 'parser' does not generate anything other than color & normal"); 72 | } 73 | GvoxRegion const region = { 74 | .range = *range, 75 | .channels = channel_flags & available_channels, 76 | .flags = 0u, 77 | .data = nullptr, 78 | }; 79 | return region; 80 | } 81 | 82 | extern "C" void procedural_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 83 | } 84 | 85 | extern "C" auto procedural_sample_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample { 86 | constexpr auto create_color = [](float rf, float gf, float bf, uint32_t const a) { 87 | uint32_t const r = static_cast(std::max(std::min(rf, 1.0f), 0.0f) * 255.0f); 88 | uint32_t const g = static_cast(std::max(std::min(gf, 1.0f), 0.0f) * 255.0f); 89 | uint32_t const b = static_cast(std::max(std::min(bf, 1.0f), 0.0f) * 255.0f); 90 | return (r << 0x00) | (g << 0x08) | (b << 0x10) | (a << 0x18); 91 | }; 92 | constexpr auto create_normal = [](float xf, float yf, float zf) { 93 | uint32_t const x = static_cast(std::max(std::min(xf * 0.5f + 0.5f, 1.0f), 0.0f) * 255.0f); 94 | uint32_t const y = static_cast(std::max(std::min(yf * 0.5f + 0.5f, 1.0f), 0.0f) * 255.0f); 95 | uint32_t const z = static_cast(std::max(std::min(zf * 0.5f + 0.5f, 1.0f), 0.0f) * 255.0f); 96 | uint32_t const w = 0; 97 | return (x << 0x00) | (y << 0x08) | (z << 0x10) | (w << 0x18); 98 | }; 99 | uint32_t color = create_color(0.6f, 0.7f, 0.9f, 0u); 100 | uint32_t normal = create_normal(0.0f, 0.0f, 0.0f); 101 | uint32_t id = 0; 102 | float const val = sample_terrain_i(offset->x, offset->y, offset->z); 103 | if (val >= 0.0f) { 104 | #if SIMPLE_TERRAIN 105 | id = 1; 106 | color = create_color(1.0f, 0.0f, 1.0f, 1u); 107 | #else 108 | { 109 | float const nx_val = sample_terrain_i(offset->x - 1, offset->y, offset->z); 110 | float const ny_val = sample_terrain_i(offset->x, offset->y - 1, offset->z); 111 | float const nz_val = sample_terrain_i(offset->x, offset->y, offset->z - 1); 112 | float const px_val = sample_terrain_i(offset->x + 1, offset->y, offset->z); 113 | float const py_val = sample_terrain_i(offset->x, offset->y + 1, offset->z); 114 | float const pz_val = sample_terrain_i(offset->x, offset->y, offset->z + 1); 115 | if (nx_val < 0.0f || ny_val < 0.0f || nz_val < 0.0f || px_val < 0.0f || py_val < 0.0f || pz_val < 0.0f) { 116 | float const nx = px_val - val; 117 | float const ny = py_val - val; 118 | float const nz = pz_val - val; 119 | float const inv_mag = 1.0f / sqrtf(nx * nx + ny * ny + nz * nz); 120 | normal = create_normal(nx * inv_mag, ny * inv_mag, nz * inv_mag); 121 | } 122 | } 123 | int si = 0; 124 | for (si = 0; si < 16; ++si) { 125 | float const s_val = sample_terrain_i(offset->x, offset->y, offset->z + si); 126 | if (s_val < -0.0f) { 127 | break; 128 | } 129 | } 130 | if (si < 2) { 131 | color = create_color(0.2f, 0.5f, 0.1f, 1u); 132 | id = 1u; 133 | } else if (si < 4) { 134 | color = create_color(0.4f, 0.3f, 0.2f, 1u); 135 | id = 2u; 136 | } else { 137 | float const r = stable_rand(offset->x, offset->y, offset->z); 138 | if (r < 0.5f) { 139 | color = create_color(0.36f, 0.34f, 0.34f, 1u); 140 | } else { 141 | color = create_color(0.25f, 0.24f, 0.23f, 1u); 142 | } 143 | id = 3u; 144 | } 145 | #endif 146 | } 147 | switch (channel_id) { 148 | case GVOX_CHANNEL_ID_COLOR: return {color, 1u}; 149 | case GVOX_CHANNEL_ID_NORMAL: return {normal, 1u}; 150 | case GVOX_CHANNEL_ID_MATERIAL_ID: return {id, 1u}; 151 | default: 152 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "Tried sampling something other than color or normal"); 153 | return {0u, 0u}; 154 | } 155 | } 156 | 157 | // Parse Driven 158 | extern "C" void procedural_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 159 | auto const available_channels = uint32_t{GVOX_CHANNEL_BIT_COLOR | GVOX_CHANNEL_BIT_NORMAL | GVOX_CHANNEL_BIT_MATERIAL_ID}; 160 | if ((channel_flags & ~available_channels) != 0) { 161 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "procedural 'parser' does not generate anything other than color & normal"); 162 | } 163 | GvoxRegion const region = { 164 | .range = *range, 165 | .channels = channel_flags & available_channels, 166 | .flags = 0u, 167 | .data = nullptr, 168 | }; 169 | gvox_emit_region(blit_ctx, ®ion); 170 | } 171 | -------------------------------------------------------------------------------- /src/adapters/parse/gvox_global_palette.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../shared/math_helpers.hpp" 13 | 14 | struct Volume { 15 | std::vector palette{}; 16 | std::vector voxels{}; 17 | }; 18 | 19 | struct GlobalPaletteParseUserState { 20 | GvoxRegionRange range{}; 21 | uint32_t channel_flags{}; 22 | uint32_t channel_n{}; 23 | size_t offset{}; 24 | std::vector channel_volumes{}; 25 | }; 26 | 27 | // Base 28 | extern "C" void gvox_parse_adapter_gvox_global_palette_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 29 | auto *user_state_ptr = malloc(sizeof(GlobalPaletteParseUserState)); 30 | new (user_state_ptr) GlobalPaletteParseUserState(); 31 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 32 | } 33 | 34 | extern "C" void gvox_parse_adapter_gvox_global_palette_destroy(GvoxAdapterContext *ctx) { 35 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 36 | user_state.~GlobalPaletteParseUserState(); 37 | free(&user_state); 38 | } 39 | 40 | extern "C" void gvox_parse_adapter_gvox_global_palette_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 41 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 42 | 43 | uint64_t magic = 0; 44 | gvox_input_read(blit_ctx, user_state.offset, sizeof(magic), &magic); 45 | user_state.offset += sizeof(magic); 46 | 47 | if (magic != std::bit_cast(std::array{'g', 'v', 'g', 'l', 'b', 'p', 'a', 'l'})) { 48 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "parsing a global palette must begin with a valid magic number"); 49 | return; 50 | } 51 | 52 | gvox_input_read(blit_ctx, user_state.offset, sizeof(GvoxRegionRange), &user_state.range); 53 | user_state.offset += sizeof(GvoxRegionRange); 54 | 55 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &user_state.channel_flags); 56 | user_state.offset += sizeof(uint32_t); 57 | 58 | user_state.channel_n = static_cast(std::popcount(user_state.channel_flags)); 59 | user_state.channel_volumes.resize(user_state.channel_n); 60 | 61 | auto voxel_n = user_state.range.extent.x * user_state.range.extent.y * user_state.range.extent.z; 62 | for (uint32_t ci = 0; ci < user_state.channel_n; ++ci) { 63 | uint32_t palette_size = 0; 64 | gvox_input_read(blit_ctx, user_state.offset, sizeof(palette_size), &palette_size); 65 | user_state.offset += sizeof(palette_size); 66 | auto &palette = user_state.channel_volumes[ci].palette; 67 | palette.resize(palette_size); 68 | } 69 | for (uint32_t ci = 0; ci < user_state.channel_n; ++ci) { 70 | auto &palette = user_state.channel_volumes[ci].palette; 71 | auto &voxels = user_state.channel_volumes[ci].voxels; 72 | auto palette_size = static_cast(palette.size()); 73 | auto bits_per_voxel = ceil_log2(palette_size); 74 | auto voxels_per_element = static_cast(8 * sizeof(voxels[0]) / bits_per_voxel); 75 | voxels.resize((voxel_n + voxels_per_element - 1) / voxels_per_element); 76 | 77 | gvox_input_read(blit_ctx, user_state.offset, palette.size() * sizeof(palette[0]), palette.data()); 78 | user_state.offset += palette.size() * sizeof(palette[0]); 79 | 80 | gvox_input_read(blit_ctx, user_state.offset, voxels.size() * sizeof(voxels[0]), voxels.data()); 81 | user_state.offset += voxels.size() * sizeof(voxels[0]); 82 | } 83 | } 84 | 85 | extern "C" void gvox_parse_adapter_gvox_global_palette_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 86 | } 87 | 88 | // General 89 | extern "C" auto gvox_parse_adapter_gvox_global_palette_query_details() -> GvoxParseAdapterDetails { 90 | return { 91 | .preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE, 92 | }; 93 | } 94 | 95 | extern "C" auto gvox_parse_adapter_gvox_global_palette_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange { 96 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 97 | return user_state.range; 98 | } 99 | 100 | extern "C" auto gvox_parse_adapter_gvox_global_palette_sample_region(GvoxBlitContext * /*blit_ctx*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample { 101 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 102 | uint32_t voxel_data = 0; 103 | uint32_t voxel_channel_index = 0; 104 | for (uint32_t channel_index = 0; channel_index < channel_id; ++channel_index) { 105 | if (((user_state.channel_flags >> channel_index) & 0x1) != 0) { 106 | ++voxel_channel_index; 107 | } 108 | } 109 | 110 | auto &palette = user_state.channel_volumes[voxel_channel_index].palette; 111 | auto &voxels = user_state.channel_volumes[voxel_channel_index].voxels; 112 | auto xi = static_cast(offset->x - user_state.range.offset.x); 113 | auto yi = static_cast(offset->y - user_state.range.offset.y); 114 | auto zi = static_cast(offset->z - user_state.range.offset.z); 115 | auto bits_per_voxel = ceil_log2(static_cast(palette.size())); 116 | auto voxels_per_element = static_cast(8 * sizeof(voxels[0]) / bits_per_voxel); 117 | 118 | auto voxel_i = xi + yi * user_state.range.extent.x + zi * user_state.range.extent.x * user_state.range.extent.y; 119 | auto voxel_mask = (1u << bits_per_voxel) - 1; 120 | auto element_i = voxel_i / voxels_per_element; 121 | auto element_offset = (voxel_i - element_i * voxels_per_element) * bits_per_voxel; 122 | voxel_data = palette[(voxels[voxel_i / voxels_per_element] >> element_offset) & voxel_mask]; 123 | 124 | return {voxel_data, 1u}; 125 | } 126 | 127 | // Serialize Driven 128 | extern "C" auto gvox_parse_adapter_gvox_global_palette_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t { 129 | return 0; 130 | } 131 | 132 | extern "C" auto gvox_parse_adapter_gvox_global_palette_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 133 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 134 | if ((channel_flags & ~user_state.channel_flags) != 0) { 135 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 136 | } 137 | GvoxRegion const region = { 138 | .range = *range, 139 | .channels = channel_flags & user_state.channel_flags, 140 | .flags = 0u, 141 | .data = nullptr, 142 | }; 143 | return region; 144 | } 145 | 146 | extern "C" void gvox_parse_adapter_gvox_global_palette_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 147 | } 148 | 149 | // Parse Driven 150 | extern "C" void gvox_parse_adapter_gvox_global_palette_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 151 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 152 | if ((channel_flags & ~user_state.channel_flags) != 0) { 153 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 154 | } 155 | GvoxRegion const region = { 156 | .range = *range, 157 | .channels = channel_flags & user_state.channel_flags, 158 | .flags = 0u, 159 | .data = nullptr, 160 | }; 161 | gvox_emit_region(blit_ctx, ®ion); 162 | } 163 | -------------------------------------------------------------------------------- /src/adapters/serialize/gvox_run_length_encoding.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | struct RunLengthEncodingUserState { 11 | GvoxRegionRange range{}; 12 | std::vector voxels; 13 | std::vector channels; 14 | size_t offset{}; 15 | }; 16 | 17 | // Base 18 | extern "C" void gvox_serialize_adapter_gvox_run_length_encoding_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 19 | auto *user_state_ptr = malloc(sizeof(RunLengthEncodingUserState)); 20 | new (user_state_ptr) RunLengthEncodingUserState(); 21 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 22 | } 23 | 24 | extern "C" void gvox_serialize_adapter_gvox_run_length_encoding_destroy(GvoxAdapterContext *ctx) { 25 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 26 | user_state.~RunLengthEncodingUserState(); 27 | free(&user_state); 28 | } 29 | 30 | extern "C" void gvox_serialize_adapter_gvox_run_length_encoding_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 31 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 32 | user_state.offset = 0; 33 | user_state.range = *range; 34 | auto magic = std::bit_cast(std::array{'r', 'l', 'e', '\0'}); 35 | gvox_output_write(blit_ctx, user_state.offset, sizeof(uint32_t), &magic); 36 | user_state.offset += sizeof(magic); 37 | gvox_output_write(blit_ctx, user_state.offset, sizeof(*range), range); 38 | user_state.offset += sizeof(*range); 39 | gvox_output_write(blit_ctx, user_state.offset, sizeof(channel_flags), &channel_flags); 40 | user_state.offset += sizeof(channel_flags); 41 | user_state.channels.resize(static_cast(std::popcount(channel_flags))); 42 | uint32_t next_channel = 0; 43 | for (uint8_t channel_i = 0; channel_i < 32; ++channel_i) { 44 | if ((channel_flags & (1u << channel_i)) != 0) { 45 | user_state.channels[next_channel] = channel_i; 46 | ++next_channel; 47 | } 48 | } 49 | user_state.voxels.resize(user_state.channels.size() * range->extent.x * range->extent.y * range->extent.z); 50 | } 51 | 52 | extern "C" void gvox_serialize_adapter_gvox_run_length_encoding_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx) { 53 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 54 | auto output = std::vector{}; 55 | output.resize(user_state.range.extent.x * user_state.range.extent.y); 56 | for (uint32_t yi = 0; yi < user_state.range.extent.y; ++yi) { 57 | for (uint32_t xi = 0; xi < user_state.range.extent.x; ++xi) { 58 | auto pointer_index = xi + yi * user_state.range.extent.x; 59 | auto column = std::vector{}; 60 | auto index = (xi + yi * user_state.range.extent.x + 0 * user_state.range.extent.x * user_state.range.extent.y) * user_state.channels.size(); 61 | for (uint32_t ci = 0; ci < user_state.channels.size(); ++ci) { 62 | column.push_back(user_state.voxels[index + ci]); 63 | } 64 | column.push_back(1); 65 | for (uint32_t zi = 1; zi < user_state.range.extent.z; ++zi) { 66 | index = (xi + yi * user_state.range.extent.x + zi * user_state.range.extent.x * user_state.range.extent.y) * user_state.channels.size(); 67 | auto col_back_index = column.size() / (user_state.channels.size() + 1) - 1; 68 | bool matched = true; 69 | for (uint32_t ci = 0; ci < user_state.channels.size(); ++ci) { 70 | auto const &test_val = user_state.voxels[index + ci]; 71 | auto const &last_val = column[col_back_index * (user_state.channels.size() + 1) + ci]; 72 | if (test_val != last_val) { 73 | matched = false; 74 | } 75 | } 76 | if (matched) { 77 | column[col_back_index * (user_state.channels.size() + 1) + user_state.channels.size()] += 1; 78 | } else { 79 | for (uint32_t ci = 0; ci < user_state.channels.size(); ++ci) { 80 | column.push_back(user_state.voxels[index + ci]); 81 | } 82 | column.push_back(1); 83 | } 84 | } 85 | auto out_index = output.size(); 86 | output[pointer_index] = static_cast(out_index); 87 | output.insert(output.end(), column.begin(), column.end()); 88 | } 89 | } 90 | gvox_output_write(blit_ctx, user_state.offset, output.size() * sizeof(output[0]), output.data()); 91 | } 92 | 93 | static void handle_region(RunLengthEncodingUserState &user_state, GvoxRegionRange const *range, auto user_func) { 94 | for (uint32_t zi = 0; zi < range->extent.z; ++zi) { 95 | for (uint32_t yi = 0; yi < range->extent.y; ++yi) { 96 | for (uint32_t xi = 0; xi < range->extent.x; ++xi) { 97 | auto const pos = GvoxOffset3D{ 98 | static_cast(xi) + range->offset.x, 99 | static_cast(yi) + range->offset.y, 100 | static_cast(zi) + range->offset.z, 101 | }; 102 | if (pos.x < user_state.range.offset.x || 103 | pos.y < user_state.range.offset.y || 104 | pos.z < user_state.range.offset.z || 105 | pos.x >= user_state.range.offset.x + static_cast(user_state.range.extent.x) || 106 | pos.y >= user_state.range.offset.y + static_cast(user_state.range.extent.y) || 107 | pos.z >= user_state.range.offset.z + static_cast(user_state.range.extent.z)) { 108 | continue; 109 | } 110 | auto output_rel_x = static_cast(pos.x - user_state.range.offset.x); 111 | auto output_rel_y = static_cast(pos.y - user_state.range.offset.y); 112 | auto output_rel_z = static_cast(pos.z - user_state.range.offset.z); 113 | for (uint32_t channel_i = 0; channel_i < user_state.channels.size(); ++channel_i) { 114 | auto output_index = static_cast(output_rel_x + output_rel_y * user_state.range.extent.x + output_rel_z * user_state.range.extent.x * user_state.range.extent.y) * user_state.channels.size() + channel_i; 115 | user_func(channel_i, output_index, pos); 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | // Serialize Driven 123 | extern "C" void gvox_serialize_adapter_gvox_run_length_encoding_serialize_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t /* channel_flags */) { 124 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 125 | handle_region( 126 | user_state, range, 127 | [blit_ctx, &user_state](uint32_t channel_i, size_t output_index, GvoxOffset3D const &pos) { 128 | auto const sample_range = GvoxRegionRange{ 129 | .offset = pos, 130 | .extent = GvoxExtent3D{1, 1, 1}, 131 | }; 132 | auto region = gvox_load_region_range(blit_ctx, &sample_range, 1u << user_state.channels[channel_i]); 133 | auto sample = gvox_sample_region(blit_ctx, ®ion, &pos, user_state.channels[channel_i]); 134 | if (sample.is_present == 0u) { 135 | sample.data = 0u; 136 | } 137 | user_state.voxels[output_index] = sample.data; 138 | gvox_unload_region_range(blit_ctx, ®ion, &sample_range); 139 | }); 140 | } 141 | 142 | // Parse Driven 143 | extern "C" void gvox_serialize_adapter_gvox_run_length_encoding_receive_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region) { 144 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 145 | handle_region( 146 | user_state, ®ion->range, 147 | [blit_ctx, region, &user_state](uint32_t channel_i, size_t output_index, GvoxOffset3D const &pos) { 148 | auto sample = gvox_sample_region(blit_ctx, region, &pos, user_state.channels[channel_i]); 149 | if (sample.is_present != 0u) { 150 | user_state.voxels[output_index] = sample.data; 151 | } 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /src/adapters/parse/kvx.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct KvxParseUserState { 14 | GvoxKvxParseAdapterConfig config{}; 15 | 16 | size_t voxdata_offset; 17 | uint32_t numbytes; 18 | uint32_t xsiz, ysiz, zsiz, xpivot, ypivot, zpivot; 19 | std::vector xoffset{}; 20 | std::vector xyoffset{}; 21 | std::vector voxdata{}; 22 | std::array, 256> palette; 23 | }; 24 | 25 | // Base 26 | extern "C" void gvox_parse_adapter_kvx_create(GvoxAdapterContext *ctx, void const *config) { 27 | auto *user_state_ptr = malloc(sizeof(KvxParseUserState)); 28 | [[maybe_unused]] auto &user_state = *(new (user_state_ptr) KvxParseUserState()); 29 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 30 | if (config != nullptr) { 31 | user_state.config = *static_cast(config); 32 | if (user_state.config.mipmaplevels == 0 || user_state.config.mipmaplevels == std::numeric_limits::max()) { 33 | user_state.config.mipmaplevels = 5; 34 | } 35 | } else { 36 | user_state.config = GvoxKvxParseAdapterConfig{ 37 | .mipmaplevels = 5, 38 | }; 39 | } 40 | } 41 | 42 | extern "C" void gvox_parse_adapter_kvx_destroy(GvoxAdapterContext *ctx) { 43 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 44 | user_state.~KvxParseUserState(); 45 | free(&user_state); 46 | } 47 | 48 | extern "C" void gvox_parse_adapter_kvx_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 49 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 50 | 51 | size_t offset = 0; 52 | auto read = [&](void *dst, size_t size) { 53 | gvox_input_read(blit_ctx, offset, size, dst); 54 | offset += size; 55 | }; 56 | 57 | for (int32_t i = 0; i < user_state.config.mipmaplevels; i++) { 58 | uint32_t numbytes; 59 | read(&numbytes, 4); 60 | if (i == 0) { 61 | user_state.numbytes = numbytes; 62 | read(&user_state.xsiz, 4); 63 | read(&user_state.ysiz, 4); 64 | read(&user_state.zsiz, 4); 65 | read(&user_state.xpivot, 4); 66 | read(&user_state.ypivot, 4); 67 | read(&user_state.zpivot, 4); 68 | user_state.xoffset.resize(user_state.xsiz + 1); 69 | read(user_state.xoffset.data(), (user_state.xsiz + 1) * 4); 70 | user_state.xyoffset.resize(user_state.xsiz * (user_state.ysiz + 1)); 71 | read(user_state.xyoffset.data(), (user_state.xsiz * (user_state.ysiz + 1)) * 2); 72 | user_state.voxdata_offset = (user_state.xsiz + 1) * 4 + user_state.xsiz * (user_state.ysiz + 1) * 2; 73 | user_state.voxdata.resize(user_state.numbytes - 24 - user_state.voxdata_offset); 74 | read(user_state.voxdata.data(), user_state.voxdata.size()); 75 | } else { 76 | offset += numbytes; 77 | } 78 | } 79 | read(user_state.palette.data(), 768); 80 | } 81 | 82 | extern "C" void gvox_parse_adapter_kvx_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 83 | } 84 | 85 | // General 86 | extern "C" auto gvox_parse_adapter_kvx_query_details() -> GvoxParseAdapterDetails { 87 | return { 88 | .preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE, 89 | }; 90 | } 91 | 92 | extern "C" auto gvox_parse_adapter_kvx_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange { 93 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 94 | return {{0, 0, 0}, {user_state.xsiz, user_state.ysiz, user_state.zsiz}}; 95 | } 96 | 97 | extern "C" auto gvox_parse_adapter_kvx_sample_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample { 98 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 99 | if (offset->x < 0 || offset->y < 0 || offset->z < 0 || 100 | static_cast(offset->x) >= user_state.xsiz || 101 | static_cast(offset->y) >= user_state.ysiz || 102 | static_cast(offset->z) >= user_state.zsiz) { 103 | return {0u, 0u}; 104 | } 105 | 106 | auto x = offset->x; 107 | auto y = user_state.ysiz - offset->y - 1; 108 | auto z = user_state.zsiz - offset->z - 1; 109 | 110 | auto start_index = user_state.xoffset[x] - user_state.voxdata_offset + user_state.xyoffset[x * (user_state.ysiz + 1) + y]; 111 | auto end_index = user_state.xoffset[x] - user_state.voxdata_offset + user_state.xyoffset[x * (user_state.ysiz + 1) + (y + 1)]; 112 | 113 | if (start_index >= user_state.voxdata.size() || end_index > user_state.voxdata.size()) { 114 | return {0u, 0u}; 115 | } 116 | auto *startptr = user_state.voxdata.data() + start_index; 117 | auto *endptr = user_state.voxdata.data() + end_index; 118 | 119 | bool is_solid = false; 120 | uint32_t color = 0x0; 121 | 122 | while (startptr < endptr) { 123 | auto slabztop = startptr[0]; 124 | auto slabzleng = startptr[1]; 125 | auto slabbackfacecullinfo = startptr[2]; 126 | startptr += 3; 127 | 128 | if (z < slabztop) { 129 | // missed voxel 130 | if (is_solid) { 131 | auto palette_index = startptr[0]; 132 | auto palette_entry = user_state.palette[palette_index]; 133 | color = uint32_t(palette_entry[0] * 4) | uint32_t(palette_entry[1] * 4 << 8) | uint32_t(palette_entry[2] * 4 << 16) | (0x1u << 24); 134 | } 135 | break; 136 | } 137 | 138 | if (slabbackfacecullinfo & 0x20) { 139 | is_solid = false; 140 | } else { 141 | is_solid = true; 142 | } 143 | 144 | if (z < slabztop + slabzleng) { 145 | // hit voxel 146 | auto palette_index = startptr[z - slabztop]; 147 | auto palette_entry = user_state.palette[palette_index]; 148 | color = uint32_t(palette_entry[0] * 4) | uint32_t(palette_entry[1] * 4 << 8) | uint32_t(palette_entry[2] * 4 << 16) | (0x1u << 24); 149 | is_solid = true; 150 | break; 151 | } 152 | 153 | startptr += slabzleng; 154 | } 155 | 156 | switch (channel_id) { 157 | case GVOX_CHANNEL_ID_COLOR: return {color, static_cast(is_solid)}; 158 | case GVOX_CHANNEL_ID_MATERIAL_ID: return {static_cast(is_solid), static_cast(is_solid)}; 159 | default: break; 160 | } 161 | return {0u, 0u}; 162 | } 163 | 164 | // Serialize Driven 165 | extern "C" auto gvox_parse_adapter_kvx_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t { 166 | return 0; 167 | } 168 | 169 | extern "C" auto gvox_parse_adapter_kvx_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 170 | uint32_t const available_channels = GVOX_CHANNEL_BIT_COLOR | GVOX_CHANNEL_BIT_MATERIAL_ID; 171 | if ((channel_flags & ~available_channels) != 0) { 172 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 173 | } 174 | GvoxRegion const region = { 175 | .range = *range, 176 | .channels = channel_flags & available_channels, 177 | .flags = 0u, 178 | .data = nullptr, 179 | }; 180 | return region; 181 | } 182 | 183 | extern "C" void gvox_parse_adapter_kvx_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 184 | } 185 | 186 | // Parse Driven 187 | extern "C" void gvox_parse_adapter_kvx_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 188 | uint32_t const available_channels = GVOX_CHANNEL_BIT_COLOR | GVOX_CHANNEL_BIT_MATERIAL_ID; 189 | if ((channel_flags & ~available_channels) != 0) { 190 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 191 | } 192 | GvoxRegion const region = { 193 | .range = *range, 194 | .channels = channel_flags & available_channels, 195 | .flags = 0u, 196 | .data = nullptr, 197 | }; 198 | gvox_emit_region(blit_ctx, ®ion); 199 | } 200 | -------------------------------------------------------------------------------- /include/gvox/gvox.h: -------------------------------------------------------------------------------- 1 | #ifndef GVOX_GVOX_H 2 | #define GVOX_GVOX_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #if !defined(GVOX_EXPORT) 12 | #define GVOX_EXPORT 13 | #endif 14 | 15 | // Consumer API 16 | 17 | typedef enum { 18 | GVOX_RESULT_SUCCESS = 0, 19 | GVOX_RESULT_ERROR_UNKNOWN = -1, 20 | GVOX_RESULT_ERROR_INVALID_PARAMETER = -2, 21 | 22 | GVOX_RESULT_ERROR_INPUT_ADAPTER = -3, 23 | GVOX_RESULT_ERROR_OUTPUT_ADAPTER = -4, 24 | GVOX_RESULT_ERROR_PARSE_ADAPTER = -5, 25 | GVOX_RESULT_ERROR_SERIALIZE_ADAPTER = -6, 26 | 27 | GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT = -7, 28 | GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT = -8, 29 | GVOX_RESULT_ERROR_SERIALIZE_ADAPTER_UNREPRESENTABLE_DATA = -9, 30 | } GvoxResult; 31 | 32 | #define GVOX_CHANNEL_ID_COLOR 0 33 | #define GVOX_CHANNEL_ID_NORMAL 1 34 | #define GVOX_CHANNEL_ID_MATERIAL_ID 2 35 | #define GVOX_CHANNEL_ID_ROUGHNESS 3 36 | #define GVOX_CHANNEL_ID_METALNESS 4 37 | #define GVOX_CHANNEL_ID_TRANSPARENCY 5 38 | #define GVOX_CHANNEL_ID_IOR 6 39 | #define GVOX_CHANNEL_ID_EMISSIVITY 7 40 | #define GVOX_CHANNEL_ID_HARDNESS 8 41 | #define GVOX_CHANNEL_ID_REFLECTIVITY 9 42 | #define GVOX_CHANNEL_ID_DENSITY 10 43 | #define GVOX_CHANNEL_ID_PHASE 11 44 | #define GVOX_CHANNEL_ID_LAST_STANDARD 23 45 | #define GVOX_CHANNEL_ID_LAST 31 46 | 47 | #define GVOX_CHANNEL_BIT_COLOR (1 << GVOX_CHANNEL_ID_COLOR) 48 | #define GVOX_CHANNEL_BIT_NORMAL (1 << GVOX_CHANNEL_ID_NORMAL) 49 | #define GVOX_CHANNEL_BIT_MATERIAL_ID (1 << GVOX_CHANNEL_ID_MATERIAL_ID) 50 | #define GVOX_CHANNEL_BIT_ROUGHNESS (1 << GVOX_CHANNEL_ID_ROUGHNESS) 51 | #define GVOX_CHANNEL_BIT_METALNESS (1 << GVOX_CHANNEL_ID_METALNESS) 52 | #define GVOX_CHANNEL_BIT_TRANSPARENCY (1 << GVOX_CHANNEL_ID_TRANSPARENCY) 53 | #define GVOX_CHANNEL_BIT_IOR (1 << GVOX_CHANNEL_ID_IOR) 54 | #define GVOX_CHANNEL_BIT_EMISSIVITY (1 << GVOX_CHANNEL_ID_EMISSIVITY) 55 | #define GVOX_CHANNEL_BIT_HARDNESS (1 << GVOX_CHANNEL_ID_HARDNESS) 56 | #define GVOX_CHANNEL_BIT_REFLECTIVITY (1 << GVOX_CHANNEL_ID_REFLECTIVITY) 57 | #define GVOX_CHANNEL_BIT_DENSITY (1 << GVOX_CHANNEL_ID_DENSITY) 58 | #define GVOX_CHANNEL_BIT_PHASE (1 << GVOX_CHANNEL_ID_PHASE) 59 | #define GVOX_CHANNEL_BIT_LAST_STANDARD (1 << GVOX_CHANNEL_ID_LAST_STANDARD) 60 | #define GVOX_CHANNEL_BIT_LAST (1 << GVOX_CHANNEL_ID_LAST) 61 | 62 | #define GVOX_REGION_FLAG_UNIFORM 0x00000001 63 | 64 | typedef struct _GvoxContext GvoxContext; 65 | typedef struct _GvoxAdapter GvoxAdapter; 66 | typedef struct _GvoxAdapterContext GvoxAdapterContext; 67 | 68 | typedef struct { 69 | uint32_t major; 70 | uint32_t minor; 71 | uint32_t patch; 72 | } GvoxVersion; 73 | 74 | typedef struct { 75 | int32_t x; 76 | int32_t y; 77 | int32_t z; 78 | } GvoxOffset3D; 79 | 80 | typedef struct { 81 | uint32_t x; 82 | uint32_t y; 83 | uint32_t z; 84 | } GvoxExtent3D; 85 | 86 | typedef struct { 87 | GvoxOffset3D offset; 88 | GvoxExtent3D extent; 89 | } GvoxRegionRange; 90 | 91 | GVOX_EXPORT void gvox_get_version(GvoxVersion *version); 92 | 93 | GVOX_EXPORT GvoxContext *gvox_create_context(void); 94 | GVOX_EXPORT void gvox_destroy_context(GvoxContext *ctx); 95 | 96 | GVOX_EXPORT GvoxResult gvox_get_result(GvoxContext *ctx); 97 | GVOX_EXPORT void gvox_get_result_message(GvoxContext *ctx, char *const str_buffer, size_t *str_size); 98 | GVOX_EXPORT void gvox_pop_result(GvoxContext *ctx); 99 | 100 | GVOX_EXPORT GvoxAdapter *gvox_get_input_adapter(GvoxContext *ctx, char const *adapter_name); 101 | GVOX_EXPORT GvoxAdapter *gvox_get_output_adapter(GvoxContext *ctx, char const *adapter_name); 102 | GVOX_EXPORT GvoxAdapter *gvox_get_parse_adapter(GvoxContext *ctx, char const *adapter_name); 103 | GVOX_EXPORT GvoxAdapter *gvox_get_serialize_adapter(GvoxContext *ctx, char const *adapter_name); 104 | 105 | GVOX_EXPORT GvoxAdapterContext *gvox_create_adapter_context(GvoxContext *gvox_ctx, GvoxAdapter *adapter, void const *config); 106 | GVOX_EXPORT void gvox_destroy_adapter_context(GvoxAdapterContext *ctx); 107 | 108 | GVOX_EXPORT void gvox_blit_region( 109 | GvoxAdapterContext *input_ctx, GvoxAdapterContext *output_ctx, 110 | GvoxAdapterContext *parse_ctx, GvoxAdapterContext *serialize_ctx, 111 | GvoxRegionRange const *requested_range, uint32_t channel_flags); 112 | 113 | GVOX_EXPORT void gvox_blit_region_parse_driven( 114 | GvoxAdapterContext *input_ctx, GvoxAdapterContext *output_ctx, 115 | GvoxAdapterContext *parse_ctx, GvoxAdapterContext *serialize_ctx, 116 | GvoxRegionRange const *requested_range, uint32_t channel_flags); 117 | 118 | GVOX_EXPORT void gvox_blit_region_serialize_driven( 119 | GvoxAdapterContext *input_ctx, GvoxAdapterContext *output_ctx, 120 | GvoxAdapterContext *parse_ctx, GvoxAdapterContext *serialize_ctx, 121 | GvoxRegionRange const *requested_range, uint32_t channel_flags); 122 | 123 | // Adapter API 124 | 125 | typedef enum { 126 | GVOX_BLIT_MODE_DONT_CARE, 127 | GVOX_BLIT_MODE_PARSE_DRIVEN, 128 | GVOX_BLIT_MODE_SERIALIZE_DRIVEN, 129 | } GvoxBlitMode; 130 | 131 | typedef struct _GvoxBlitContext GvoxBlitContext; 132 | 133 | typedef struct { 134 | GvoxRegionRange range; 135 | uint32_t channels; 136 | uint32_t flags; 137 | void const *data; 138 | } GvoxRegion; 139 | 140 | typedef struct { 141 | uint32_t data; 142 | uint8_t is_present; 143 | } GvoxSample; 144 | 145 | typedef struct { 146 | GvoxBlitMode preferred_blit_mode; 147 | } GvoxParseAdapterDetails; 148 | 149 | typedef struct { 150 | char const *name_str; 151 | void (*create)(GvoxAdapterContext *ctx, void const *config); 152 | void (*destroy)(GvoxAdapterContext *ctx); 153 | void (*blit_begin)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 154 | void (*blit_end)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx); 155 | } GvoxAdapterBaseInfo; 156 | 157 | typedef struct { 158 | GvoxAdapterBaseInfo base_info; 159 | void (*read)(GvoxAdapterContext *ctx, size_t position, size_t size, void *data); 160 | } GvoxInputAdapterInfo; 161 | 162 | typedef struct { 163 | GvoxAdapterBaseInfo base_info; 164 | void (*write)(GvoxAdapterContext *ctx, size_t position, size_t size, void const *data); 165 | void (*reserve)(GvoxAdapterContext *ctx, size_t size); 166 | } GvoxOutputAdapterInfo; 167 | 168 | typedef struct { 169 | GvoxAdapterBaseInfo base_info; 170 | // General 171 | GvoxParseAdapterDetails (*query_details)(void); 172 | GvoxRegionRange (*query_parsable_range)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx); 173 | GvoxSample (*sample_region)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region, GvoxOffset3D const *offset, uint32_t channel_id); 174 | // Serialize Driven 175 | uint32_t (*query_region_flags)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 176 | GvoxRegion (*load_region)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 177 | void (*unload_region)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion *region); 178 | // Parse Driven 179 | void (*parse_region)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 180 | } GvoxParseAdapterInfo; 181 | 182 | typedef struct { 183 | GvoxAdapterBaseInfo base_info; 184 | // Serialize Driven 185 | void (*serialize_region)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 186 | // Parse Driven 187 | void (*receive_region)(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region); 188 | } GvoxSerializeAdapterInfo; 189 | 190 | GVOX_EXPORT GvoxAdapter *gvox_register_input_adapter(GvoxContext *ctx, GvoxInputAdapterInfo const *adapter_info); 191 | GVOX_EXPORT GvoxAdapter *gvox_register_output_adapter(GvoxContext *ctx, GvoxOutputAdapterInfo const *adapter_info); 192 | GVOX_EXPORT GvoxAdapter *gvox_register_parse_adapter(GvoxContext *ctx, GvoxParseAdapterInfo const *adapter_info); 193 | GVOX_EXPORT GvoxAdapter *gvox_register_serialize_adapter(GvoxContext *ctx, GvoxSerializeAdapterInfo const *adapter_info); 194 | 195 | GVOX_EXPORT uint32_t gvox_query_region_flags(GvoxBlitContext *blit_ctx, GvoxRegionRange const *range, uint32_t channel_flags); 196 | GVOX_EXPORT GvoxRegion gvox_load_region_range(GvoxBlitContext *blit_ctx, GvoxRegionRange const *range, uint32_t channel_flags); 197 | GVOX_EXPORT void gvox_unload_region_range(GvoxBlitContext *blit_ctx, GvoxRegion *region, GvoxRegionRange const *range); 198 | GVOX_EXPORT GvoxSample gvox_sample_region(GvoxBlitContext *blit_ctx, GvoxRegion const *region, GvoxOffset3D const *offset, uint32_t channel_id); 199 | 200 | GVOX_EXPORT void gvox_adapter_push_error(GvoxAdapterContext *ctx, GvoxResult result_code, char const *message); 201 | GVOX_EXPORT void gvox_adapter_set_user_pointer(GvoxAdapterContext *ctx, void *ptr); 202 | GVOX_EXPORT void *gvox_adapter_get_user_pointer(GvoxAdapterContext *ctx); 203 | 204 | GVOX_EXPORT void gvox_input_read(GvoxBlitContext *blit_ctx, size_t position, size_t size, void *data); 205 | GVOX_EXPORT void gvox_output_write(GvoxBlitContext *blit_ctx, size_t position, size_t size, void const *data); 206 | GVOX_EXPORT void gvox_output_reserve(GvoxBlitContext *blit_ctx, size_t size); 207 | 208 | GVOX_EXPORT void gvox_emit_region(GvoxBlitContext *blit_ctx, GvoxRegion const *region); 209 | 210 | #ifdef __cplusplus 211 | } 212 | #endif 213 | 214 | #endif 215 | -------------------------------------------------------------------------------- /cmake/adapters.cmake: -------------------------------------------------------------------------------- 1 | 2 | foreach(NAME ${GVOX_INPUT_ADAPTERS}) 3 | target_sources(${PROJECT_NAME} PRIVATE "src/adapters/input/${NAME}.cpp") 4 | set(INPUT_ADAPTER_NAMES_CONTENT "${INPUT_ADAPTER_NAMES_CONTENT} 5 | \"${NAME}\",") 6 | set(INPUT_ADAPTER_INFOS_CONTENT "${INPUT_ADAPTER_INFOS_CONTENT} 7 | GvoxInputAdapterInfo{ 8 | .base_info = { 9 | .name_str = \"${NAME}\", 10 | .create = gvox_input_adapter_${NAME}_create, 11 | .destroy = gvox_input_adapter_${NAME}_destroy, 12 | .blit_begin = gvox_input_adapter_${NAME}_blit_begin, 13 | .blit_end = gvox_input_adapter_${NAME}_blit_end, 14 | }, 15 | .read = gvox_input_adapter_${NAME}_read, 16 | },") 17 | endforeach() 18 | foreach(NAME ${GVOX_OUTPUT_ADAPTERS}) 19 | target_sources(${PROJECT_NAME} PRIVATE "src/adapters/output/${NAME}.cpp") 20 | set(OUTPUT_ADAPTER_NAMES_CONTENT "${OUTPUT_ADAPTER_NAMES_CONTENT} 21 | \"${NAME}\",") 22 | set(OUTPUT_ADAPTER_INFOS_CONTENT "${OUTPUT_ADAPTER_INFOS_CONTENT} 23 | GvoxOutputAdapterInfo{ 24 | .base_info = { 25 | .name_str = \"${NAME}\", 26 | .create = gvox_output_adapter_${NAME}_create, 27 | .destroy = gvox_output_adapter_${NAME}_destroy, 28 | .blit_begin = gvox_output_adapter_${NAME}_blit_begin, 29 | .blit_end = gvox_output_adapter_${NAME}_blit_end, 30 | }, 31 | .write = gvox_output_adapter_${NAME}_write, 32 | .reserve = gvox_output_adapter_${NAME}_reserve, 33 | },") 34 | endforeach() 35 | foreach(NAME ${GVOX_PARSE_ADAPTERS}) 36 | target_sources(${PROJECT_NAME} PRIVATE "src/adapters/parse/${NAME}.cpp") 37 | set(PARSE_ADAPTER_NAMES_CONTENT "${PARSE_ADAPTER_NAMES_CONTENT} 38 | \"${NAME}\",") 39 | set(PARSE_ADAPTER_INFOS_CONTENT "${PARSE_ADAPTER_INFOS_CONTENT} 40 | GvoxParseAdapterInfo{ 41 | .base_info = { 42 | .name_str = \"${NAME}\", 43 | .create = gvox_parse_adapter_${NAME}_create, 44 | .destroy = gvox_parse_adapter_${NAME}_destroy, 45 | .blit_begin = gvox_parse_adapter_${NAME}_blit_begin, 46 | .blit_end = gvox_parse_adapter_${NAME}_blit_end, 47 | }, 48 | 49 | .query_details = gvox_parse_adapter_${NAME}_query_details, 50 | .query_parsable_range= gvox_parse_adapter_${NAME}_query_parsable_range, 51 | .sample_region = gvox_parse_adapter_${NAME}_sample_region, 52 | 53 | .query_region_flags = gvox_parse_adapter_${NAME}_query_region_flags, 54 | .load_region = gvox_parse_adapter_${NAME}_load_region, 55 | .unload_region = gvox_parse_adapter_${NAME}_unload_region, 56 | 57 | .parse_region = gvox_parse_adapter_${NAME}_parse_region, 58 | },") 59 | endforeach() 60 | foreach(NAME ${GVOX_SERIALIZE_ADAPTERS}) 61 | target_sources(${PROJECT_NAME} PRIVATE "src/adapters/serialize/${NAME}.cpp") 62 | set(SERIALIZE_ADAPTER_NAMES_CONTENT "${SERIALIZE_ADAPTER_NAMES_CONTENT} 63 | \"${NAME}\",") 64 | set(SERIALIZE_ADAPTER_INFOS_CONTENT "${SERIALIZE_ADAPTER_INFOS_CONTENT} 65 | GvoxSerializeAdapterInfo{ 66 | .base_info = { 67 | .name_str = \"${NAME}\", 68 | .create = gvox_serialize_adapter_${NAME}_create, 69 | .destroy = gvox_serialize_adapter_${NAME}_destroy, 70 | .blit_begin = gvox_serialize_adapter_${NAME}_blit_begin, 71 | .blit_end = gvox_serialize_adapter_${NAME}_blit_end, 72 | }, 73 | 74 | .serialize_region = gvox_serialize_adapter_${NAME}_serialize_region, 75 | 76 | .receive_region = gvox_serialize_adapter_${NAME}_receive_region, 77 | },") 78 | endforeach() 79 | 80 | set(ADAPTERS_HEADER_CONTENT "#pragma once 81 | 82 | #include 83 | #include 84 | #include 85 | 86 | static constexpr std::array input_adapter_names = {${INPUT_ADAPTER_NAMES_CONTENT} 87 | }; 88 | static constexpr std::array output_adapter_names = {${OUTPUT_ADAPTER_NAMES_CONTENT} 89 | }; 90 | static constexpr std::array parse_adapter_names = {${PARSE_ADAPTER_NAMES_CONTENT} 91 | }; 92 | static constexpr std::array serialize_adapter_names = {${SERIALIZE_ADAPTER_NAMES_CONTENT} 93 | }; 94 | ") 95 | 96 | foreach(NAME ${GVOX_INPUT_ADAPTERS}) 97 | string(MAKE_C_IDENTIFIER "${NAME}" NAME_UPPER) 98 | string(TOUPPER "${NAME_UPPER}" NAME_UPPER) 99 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_INPUT_ADAPTER_${NAME_UPPER}_BUILT_STATIC=1) 100 | set(ADAPTERS_HEADER_CONTENT "${ADAPTERS_HEADER_CONTENT} 101 | extern \"C\" void gvox_input_adapter_${NAME}_create(GvoxAdapterContext *ctx, void const *config); 102 | extern \"C\" void gvox_input_adapter_${NAME}_destroy(GvoxAdapterContext *ctx); 103 | extern \"C\" void gvox_input_adapter_${NAME}_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 104 | extern \"C\" void gvox_input_adapter_${NAME}_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx); 105 | 106 | extern \"C\" void gvox_input_adapter_${NAME}_read(GvoxAdapterContext *ctx, size_t position, size_t size, void *data); 107 | ") 108 | endforeach() 109 | foreach(NAME ${GVOX_OUTPUT_ADAPTERS}) 110 | string(MAKE_C_IDENTIFIER "${NAME}" NAME_UPPER) 111 | string(TOUPPER "${NAME_UPPER}" NAME_UPPER) 112 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_OUTPUT_ADAPTER_${NAME_UPPER}_BUILT_STATIC=1) 113 | set(ADAPTERS_HEADER_CONTENT "${ADAPTERS_HEADER_CONTENT} 114 | extern \"C\" void gvox_output_adapter_${NAME}_create(GvoxAdapterContext *ctx, void const *config); 115 | extern \"C\" void gvox_output_adapter_${NAME}_destroy(GvoxAdapterContext *ctx); 116 | extern \"C\" void gvox_output_adapter_${NAME}_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 117 | extern \"C\" void gvox_output_adapter_${NAME}_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx); 118 | 119 | extern \"C\" void gvox_output_adapter_${NAME}_write(GvoxAdapterContext *ctx, size_t position, size_t size, void const *data); 120 | extern \"C\" void gvox_output_adapter_${NAME}_reserve(GvoxAdapterContext *ctx, size_t size); 121 | ") 122 | endforeach() 123 | foreach(NAME ${GVOX_PARSE_ADAPTERS}) 124 | string(MAKE_C_IDENTIFIER "${NAME}" NAME_UPPER) 125 | string(TOUPPER "${NAME_UPPER}" NAME_UPPER) 126 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_PARSE_ADAPTER_${NAME_UPPER}_BUILT_STATIC=1) 127 | set(ADAPTERS_HEADER_CONTENT "${ADAPTERS_HEADER_CONTENT} 128 | extern \"C\" void gvox_parse_adapter_${NAME}_create(GvoxAdapterContext *ctx, void const *config); 129 | extern \"C\" void gvox_parse_adapter_${NAME}_destroy(GvoxAdapterContext *ctx); 130 | extern \"C\" void gvox_parse_adapter_${NAME}_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 131 | extern \"C\" void gvox_parse_adapter_${NAME}_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx); 132 | 133 | extern \"C\" auto gvox_parse_adapter_${NAME}_query_details() -> GvoxParseAdapterDetails; 134 | extern \"C\" auto gvox_parse_adapter_${NAME}_query_parsable_range(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx) -> GvoxRegionRange; 135 | extern \"C\" auto gvox_parse_adapter_${NAME}_sample_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample; 136 | 137 | extern \"C\" auto gvox_parse_adapter_${NAME}_query_region_flags(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> uint32_t; 138 | extern \"C\" auto gvox_parse_adapter_${NAME}_load_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion; 139 | extern \"C\" void gvox_parse_adapter_${NAME}_unload_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion *region); 140 | 141 | extern \"C\" void gvox_parse_adapter_${NAME}_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 142 | ") 143 | endforeach() 144 | foreach(NAME ${GVOX_SERIALIZE_ADAPTERS}) 145 | string(MAKE_C_IDENTIFIER "${NAME}" NAME_UPPER) 146 | string(TOUPPER "${NAME_UPPER}" NAME_UPPER) 147 | target_compile_definitions(${PROJECT_NAME} PRIVATE GVOX_SERIALIZE_ADAPTER_${NAME_UPPER}_BUILT_STATIC=1) 148 | set(ADAPTERS_HEADER_CONTENT "${ADAPTERS_HEADER_CONTENT} 149 | extern \"C\" void gvox_serialize_adapter_${NAME}_create(GvoxAdapterContext *ctx, void const *config); 150 | extern \"C\" void gvox_serialize_adapter_${NAME}_destroy(GvoxAdapterContext *ctx); 151 | extern \"C\" void gvox_serialize_adapter_${NAME}_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 152 | extern \"C\" void gvox_serialize_adapter_${NAME}_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx); 153 | 154 | extern \"C\" void gvox_serialize_adapter_${NAME}_serialize_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags); 155 | 156 | extern \"C\" void gvox_serialize_adapter_${NAME}_receive_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region); 157 | ") 158 | endforeach() 159 | 160 | set(ADAPTERS_HEADER_CONTENT "${ADAPTERS_HEADER_CONTENT} 161 | static constexpr std::array input_adapter_infos = {${INPUT_ADAPTER_INFOS_CONTENT} 162 | }; 163 | static constexpr std::array output_adapter_infos = {${OUTPUT_ADAPTER_INFOS_CONTENT} 164 | }; 165 | static constexpr std::array parse_adapter_infos = {${PARSE_ADAPTER_INFOS_CONTENT} 166 | }; 167 | static constexpr std::array serialize_adapter_infos = {${SERIALIZE_ADAPTER_INFOS_CONTENT} 168 | }; 169 | ") 170 | 171 | file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/adapters.hpp" "${ADAPTERS_HEADER_CONTENT}") 172 | -------------------------------------------------------------------------------- /src/adapters/parse/gvox_run_length_encoding.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct RunLengthEncodingParseUserState { 13 | GvoxRegionRange range{}; 14 | uint32_t channel_flags{}; 15 | uint32_t channel_n{}; 16 | size_t offset{}; 17 | 18 | std::vector column_pointers{}; 19 | std::vector> columns{}; 20 | }; 21 | 22 | #define CACHE_COLUMNS 0 23 | 24 | // Base 25 | extern "C" void gvox_parse_adapter_gvox_run_length_encoding_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 26 | auto *user_state_ptr = malloc(sizeof(RunLengthEncodingParseUserState)); 27 | new (user_state_ptr) RunLengthEncodingParseUserState(); 28 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 29 | } 30 | 31 | extern "C" void gvox_parse_adapter_gvox_run_length_encoding_destroy(GvoxAdapterContext *ctx) { 32 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 33 | user_state.~RunLengthEncodingParseUserState(); 34 | free(&user_state); 35 | } 36 | 37 | extern "C" void gvox_parse_adapter_gvox_run_length_encoding_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 38 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 39 | 40 | uint32_t magic = 0; 41 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &magic); 42 | user_state.offset += sizeof(uint32_t); 43 | 44 | if (magic != std::bit_cast(std::array{'r', 'l', 'e', '\0'})) { 45 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "parsing a RLE format must begin with a valid magic number"); 46 | return; 47 | } 48 | 49 | gvox_input_read(blit_ctx, user_state.offset, sizeof(GvoxRegionRange), &user_state.range); 50 | user_state.offset += sizeof(GvoxRegionRange); 51 | 52 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &user_state.channel_flags); 53 | user_state.offset += sizeof(uint32_t); 54 | 55 | user_state.channel_n = static_cast(std::popcount(user_state.channel_flags)); 56 | 57 | user_state.column_pointers.resize(static_cast(user_state.range.extent.x) * user_state.range.extent.y); 58 | user_state.columns.resize(static_cast(user_state.range.extent.x) * user_state.range.extent.y); 59 | gvox_input_read(blit_ctx, user_state.offset, user_state.column_pointers.size() * sizeof(user_state.column_pointers[0]), user_state.column_pointers.data()); 60 | // user_state.offset += user_state.column_pointers.size() * sizeof(user_state.column_pointers[0]); 61 | #if !CACHE_COLUMNS 62 | for (uint32_t yi = 0; yi < user_state.range.extent.y; ++yi) { 63 | for (uint32_t xi = 0; xi < user_state.range.extent.x; ++xi) { 64 | auto &column = user_state.columns[xi + yi * user_state.range.extent.x]; 65 | auto column_ptr = user_state.column_pointers[xi + yi * user_state.range.extent.x]; 66 | uint32_t zi = 0; 67 | uint32_t read_offset = column_ptr * sizeof(uint32_t) + static_cast(user_state.offset); 68 | while (zi < user_state.range.extent.z) { 69 | for (uint32_t ci = 0; ci < user_state.channel_n; ++ci) { 70 | column.push_back(0); 71 | auto &voxel_data = column.back(); 72 | gvox_input_read(blit_ctx, read_offset + ci, sizeof(voxel_data), &voxel_data); 73 | } 74 | read_offset += user_state.channel_n * sizeof(uint32_t); 75 | uint32_t run_length = 0; 76 | gvox_input_read(blit_ctx, read_offset, sizeof(run_length), &run_length); 77 | read_offset += 1 * sizeof(uint32_t); 78 | column.push_back(run_length); 79 | zi += run_length; 80 | } 81 | } 82 | } 83 | #endif 84 | } 85 | 86 | extern "C" void gvox_parse_adapter_gvox_run_length_encoding_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 87 | } 88 | 89 | // General 90 | extern "C" auto gvox_parse_adapter_gvox_run_length_encoding_query_details() -> GvoxParseAdapterDetails { 91 | return { 92 | .preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE, 93 | }; 94 | } 95 | 96 | extern "C" auto gvox_parse_adapter_gvox_run_length_encoding_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange { 97 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 98 | return user_state.range; 99 | } 100 | 101 | extern "C" auto gvox_parse_adapter_gvox_run_length_encoding_sample_region(GvoxBlitContext * /*blit_ctx*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample { 102 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 103 | auto x_pos = static_cast(offset->x - user_state.range.offset.x); 104 | auto y_pos = static_cast(offset->y - user_state.range.offset.y); 105 | auto z_pos = static_cast(offset->z - user_state.range.offset.z); 106 | auto &column = user_state.columns[x_pos + y_pos * user_state.range.extent.x]; 107 | uint32_t voxel_data = 0; 108 | uint32_t voxel_channel_index = 0; 109 | for (uint32_t channel_index = 0; channel_index < channel_id; ++channel_index) { 110 | if (((user_state.channel_flags >> channel_index) & 0x1) != 0) { 111 | ++voxel_channel_index; 112 | } 113 | } 114 | #if CACHE_COLUMNS 115 | if (column.size() == 0) { 116 | // load column 117 | auto column_ptr = user_state.column_pointers[x_pos + y_pos * user_state.range.extent.x]; 118 | column.resize(user_state.channel_n * user_state.range.extent.z); 119 | uint32_t zi = 0; 120 | uint32_t read_offset = column_ptr * sizeof(uint32_t) + static_cast(user_state.offset); 121 | while (zi < user_state.range.extent.z) { 122 | for (uint32_t ci = 0; ci < user_state.channel_n; ++ci) { 123 | auto &voxel_data = column[ci + zi * user_state.channel_n]; 124 | gvox_input_read(blit_ctx, read_offset + ci, sizeof(voxel_data), &voxel_data); 125 | } 126 | read_offset += user_state.channel_n * sizeof(uint32_t); 127 | uint32_t run_length = 0; 128 | gvox_input_read(blit_ctx, read_offset, sizeof(run_length), &run_length); 129 | read_offset += 1 * sizeof(uint32_t); 130 | for (uint32_t ri = 1; ri < run_length; ++ri) { 131 | for (uint32_t ci = 0; ci < user_state.channel_n; ++ci) { 132 | auto &voxel_data = column[ci + zi * user_state.channel_n]; 133 | column[ci + (zi + ri) * user_state.channel_n] = voxel_data; 134 | } 135 | } 136 | zi += run_length; 137 | } 138 | } 139 | voxel_data = column[z_pos * user_state.channel_n + voxel_channel_index]; 140 | #else 141 | uint32_t curr_z = 0; 142 | for (uint32_t i = 0; i < column.size(); i += user_state.channel_n + 1) { 143 | auto run_length = column[i + user_state.channel_n]; 144 | auto next_z = curr_z + run_length; 145 | if (z_pos < next_z) { 146 | voxel_data = column[i + voxel_channel_index]; 147 | break; 148 | } 149 | curr_z = next_z; 150 | } 151 | #endif 152 | return {voxel_data, 1u}; 153 | } 154 | 155 | // Serialize Driven 156 | extern "C" auto gvox_parse_adapter_gvox_run_length_encoding_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t { 157 | return 0; 158 | } 159 | 160 | extern "C" auto gvox_parse_adapter_gvox_run_length_encoding_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 161 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 162 | if ((channel_flags & ~user_state.channel_flags) != 0) { 163 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 164 | } 165 | GvoxRegion const region = { 166 | .range = *range, 167 | .channels = channel_flags & user_state.channel_flags, 168 | .flags = 0u, 169 | .data = nullptr, 170 | }; 171 | return region; 172 | } 173 | 174 | extern "C" void gvox_parse_adapter_gvox_run_length_encoding_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 175 | } 176 | 177 | // Parse Driven 178 | extern "C" void gvox_parse_adapter_gvox_run_length_encoding_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 179 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 180 | if ((channel_flags & ~user_state.channel_flags) != 0) { 181 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 182 | } 183 | GvoxRegion const region = { 184 | .range = *range, 185 | .channels = channel_flags & user_state.channel_flags, 186 | .flags = 0u, 187 | .data = nullptr, 188 | }; 189 | gvox_emit_region(blit_ctx, ®ion); 190 | } 191 | -------------------------------------------------------------------------------- /src/adapters/serialize/gvox_global_palette.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../shared/math_helpers.hpp" 13 | #include "../shared/thread_pool.hpp" 14 | 15 | struct GlobalPaletteUserState { 16 | GvoxRegionRange range{}; 17 | std::vector voxels; 18 | std::vector channels; 19 | size_t offset{}; 20 | std::vector> unique_values; 21 | #if GVOX_ENABLE_MULTITHREADED_ADAPTERS && GVOX_ENABLE_THREADSAFETY 22 | std::mutex palette_mutex; 23 | #endif 24 | }; 25 | 26 | // Base 27 | extern "C" void gvox_serialize_adapter_gvox_global_palette_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 28 | auto *user_state_ptr = malloc(sizeof(GlobalPaletteUserState)); 29 | new (user_state_ptr) GlobalPaletteUserState(); 30 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 31 | } 32 | 33 | extern "C" void gvox_serialize_adapter_gvox_global_palette_destroy(GvoxAdapterContext *ctx) { 34 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 35 | user_state.~GlobalPaletteUserState(); 36 | free(&user_state); 37 | } 38 | 39 | extern "C" void gvox_serialize_adapter_gvox_global_palette_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 40 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 41 | user_state.offset = 0; 42 | user_state.range = *range; 43 | auto magic = std::bit_cast(std::array{'g', 'v', 'g', 'l', 'b', 'p', 'a', 'l'}); 44 | gvox_output_write(blit_ctx, user_state.offset, sizeof(magic), &magic); 45 | user_state.offset += sizeof(magic); 46 | gvox_output_write(blit_ctx, user_state.offset, sizeof(*range), range); 47 | user_state.offset += sizeof(*range); 48 | gvox_output_write(blit_ctx, user_state.offset, sizeof(channel_flags), &channel_flags); 49 | user_state.offset += sizeof(channel_flags); 50 | user_state.channels.resize(static_cast(std::popcount(channel_flags))); 51 | uint32_t next_channel = 0; 52 | for (uint8_t channel_i = 0; channel_i < 32; ++channel_i) { 53 | if ((channel_flags & (1u << channel_i)) != 0) { 54 | user_state.channels[next_channel] = channel_i; 55 | ++next_channel; 56 | } 57 | } 58 | user_state.unique_values.resize(user_state.channels.size()); 59 | for (auto &unique_values : user_state.unique_values) { 60 | unique_values.insert(0u); 61 | } 62 | user_state.voxels.resize(user_state.channels.size() * range->extent.x * range->extent.y * range->extent.z); 63 | } 64 | 65 | extern "C" void gvox_serialize_adapter_gvox_global_palette_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx) { 66 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 67 | 68 | auto sorted_unique_values_lists = std::vector>{}; 69 | sorted_unique_values_lists.resize(user_state.channels.size()); 70 | 71 | for (size_t i = 0; i < user_state.channels.size(); ++i) { 72 | auto set_size = static_cast(user_state.unique_values[i].size()); 73 | sorted_unique_values_lists[i].reserve(set_size); 74 | gvox_output_write(blit_ctx, user_state.offset, sizeof(set_size), &set_size); 75 | user_state.offset += sizeof(set_size); 76 | for (auto const &val : user_state.unique_values[i]) { 77 | sorted_unique_values_lists[i].push_back(val); 78 | } 79 | } 80 | 81 | auto voxel_n = user_state.range.extent.x * user_state.range.extent.y * user_state.range.extent.z; 82 | 83 | for (size_t ci = 0; ci < user_state.channels.size(); ++ci) { 84 | auto &sorted_unique_values = sorted_unique_values_lists[ci]; 85 | std::sort(sorted_unique_values.begin(), sorted_unique_values.end()); 86 | 87 | std::vector bits{}; 88 | auto bits_per_voxel = ceil_log2(static_cast(sorted_unique_values.size())); 89 | auto voxels_per_element = 8 * sizeof(bits[0]) / bits_per_voxel; 90 | bits.resize((voxel_n + voxels_per_element - 1) / voxels_per_element); 91 | auto voxel_mask = (1u << bits_per_voxel) - 1; 92 | 93 | for (uint32_t zi = 0; zi < user_state.range.extent.z; ++zi) { 94 | for (uint32_t yi = 0; yi < user_state.range.extent.y; ++yi) { 95 | for (uint32_t xi = 0; xi < user_state.range.extent.x; ++xi) { 96 | auto voxel_i = xi + yi * user_state.range.extent.x + zi * user_state.range.extent.x * user_state.range.extent.y; 97 | auto element_i = voxel_i / voxels_per_element; 98 | auto element_offset = (voxel_i - element_i * voxels_per_element) * bits_per_voxel; 99 | auto my_voxel = user_state.voxels[voxel_i * user_state.channels.size() + ci]; 100 | auto palette_index = static_cast(std::lower_bound(sorted_unique_values.begin(), sorted_unique_values.end(), my_voxel) - sorted_unique_values.begin()); 101 | bits[element_i] |= (palette_index & voxel_mask) << element_offset; 102 | } 103 | } 104 | } 105 | 106 | gvox_output_write(blit_ctx, user_state.offset, sorted_unique_values.size() * sizeof(sorted_unique_values[0]), sorted_unique_values.data()); 107 | user_state.offset += sorted_unique_values.size() * sizeof(sorted_unique_values[0]); 108 | 109 | gvox_output_write(blit_ctx, user_state.offset, bits.size() * sizeof(bits[0]), bits.data()); 110 | user_state.offset += bits.size() * sizeof(bits[0]); 111 | } 112 | } 113 | 114 | static void handle_region(GlobalPaletteUserState &user_state, GvoxRegionRange const *range, auto user_func) { 115 | for (uint32_t zi = 0; zi < range->extent.z; ++zi) { 116 | for (uint32_t yi = 0; yi < range->extent.y; ++yi) { 117 | for (uint32_t xi = 0; xi < range->extent.x; ++xi) { 118 | auto const pos = GvoxOffset3D{ 119 | static_cast(xi) + range->offset.x, 120 | static_cast(yi) + range->offset.y, 121 | static_cast(zi) + range->offset.z, 122 | }; 123 | if (pos.x < user_state.range.offset.x || 124 | pos.y < user_state.range.offset.y || 125 | pos.z < user_state.range.offset.z || 126 | pos.x >= user_state.range.offset.x + static_cast(user_state.range.extent.x) || 127 | pos.y >= user_state.range.offset.y + static_cast(user_state.range.extent.y) || 128 | pos.z >= user_state.range.offset.z + static_cast(user_state.range.extent.z)) { 129 | continue; 130 | } 131 | auto output_rel_x = static_cast(pos.x - user_state.range.offset.x); 132 | auto output_rel_y = static_cast(pos.y - user_state.range.offset.y); 133 | auto output_rel_z = static_cast(pos.z - user_state.range.offset.z); 134 | for (uint32_t channel_i = 0; channel_i < user_state.channels.size(); ++channel_i) { 135 | auto output_index = static_cast(output_rel_x + output_rel_y * user_state.range.extent.x + output_rel_z * user_state.range.extent.x * user_state.range.extent.y) * user_state.channels.size() + channel_i; 136 | user_func(channel_i, output_index, pos); 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | // Serialize Driven 144 | extern "C" void gvox_serialize_adapter_gvox_global_palette_serialize_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t /* channel_flags */) { 145 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 146 | handle_region( 147 | user_state, range, 148 | [blit_ctx, &user_state](uint32_t channel_i, size_t output_index, GvoxOffset3D const &pos) { 149 | auto const sample_range = GvoxRegionRange{ 150 | .offset = pos, 151 | .extent = GvoxExtent3D{1, 1, 1}, 152 | }; 153 | auto region = gvox_load_region_range(blit_ctx, &sample_range, 1u << user_state.channels[channel_i]); 154 | auto sample = gvox_sample_region(blit_ctx, ®ion, &pos, user_state.channels[channel_i]); 155 | if (sample.is_present == 0u) { 156 | sample.data = 0u; 157 | } 158 | user_state.voxels[output_index] = sample.data; 159 | user_state.unique_values[channel_i].insert(sample.data); 160 | gvox_unload_region_range(blit_ctx, ®ion, &sample_range); 161 | }); 162 | } 163 | 164 | // Parse Driven 165 | extern "C" void gvox_serialize_adapter_gvox_global_palette_receive_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region) { 166 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 167 | handle_region( 168 | user_state, ®ion->range, 169 | [blit_ctx, region, &user_state](uint32_t channel_i, size_t output_index, GvoxOffset3D const &pos) { 170 | auto sample = gvox_sample_region(blit_ctx, region, &pos, user_state.channels[channel_i]); 171 | if (sample.is_present != 0u) { 172 | user_state.voxels[output_index] = sample.data; 173 | #if GVOX_ENABLE_MULTITHREADED_ADAPTERS && GVOX_ENABLE_THREADSAFETY 174 | auto lock = std::lock_guard{user_state.palette_mutex}; 175 | #endif 176 | user_state.unique_values[channel_i].insert(sample.data); 177 | } 178 | }); 179 | } 180 | -------------------------------------------------------------------------------- /src/adapters/serialize/gvox_brickmap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | // #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../shared/gvox_brickmap.hpp" 11 | #include "../shared/thread_pool.hpp" 12 | 13 | struct TempBrickInfo { 14 | uint32_t first_voxel{}; 15 | uint32_t state = 0; 16 | #if GVOX_ENABLE_MULTITHREADED_ADAPTERS 17 | std::mutex access_mtx; 18 | #endif 19 | }; 20 | 21 | struct BrickmapUserState { 22 | GvoxRegionRange range{}; 23 | std::vector voxels; 24 | std::vector channels; 25 | size_t offset{}; 26 | GvoxExtent3D bricks_extent{}; 27 | std::unique_ptr> temp_brick_infos{}; 28 | uint32_t brick_heap_size{}; 29 | }; 30 | 31 | // Base 32 | extern "C" void gvox_serialize_adapter_gvox_brickmap_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 33 | auto *user_state_ptr = malloc(sizeof(BrickmapUserState)); 34 | new (user_state_ptr) BrickmapUserState(); 35 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 36 | } 37 | 38 | extern "C" void gvox_serialize_adapter_gvox_brickmap_destroy(GvoxAdapterContext *ctx) { 39 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 40 | user_state.~BrickmapUserState(); 41 | free(&user_state); 42 | } 43 | 44 | extern "C" void gvox_serialize_adapter_gvox_brickmap_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 45 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 46 | user_state.offset = 0; 47 | user_state.range = *range; 48 | auto magic = std::bit_cast(std::array{'b', 'r', 'k', '\0'}); 49 | gvox_output_write(blit_ctx, user_state.offset, sizeof(uint32_t), &magic); 50 | user_state.offset += sizeof(magic); 51 | gvox_output_write(blit_ctx, user_state.offset, sizeof(*range), range); 52 | user_state.offset += sizeof(*range); 53 | gvox_output_write(blit_ctx, user_state.offset, sizeof(channel_flags), &channel_flags); 54 | user_state.offset += sizeof(channel_flags); 55 | user_state.channels.resize(static_cast(std::popcount(channel_flags))); 56 | uint32_t next_channel = 0; 57 | for (uint8_t channel_i = 0; channel_i < 32; ++channel_i) { 58 | if ((channel_flags & (1u << channel_i)) != 0) { 59 | user_state.channels[next_channel] = channel_i; 60 | ++next_channel; 61 | } 62 | } 63 | user_state.voxels.resize(user_state.channels.size() * range->extent.x * range->extent.y * range->extent.z); 64 | user_state.bricks_extent.x = (range->extent.x + 7) / 8; 65 | user_state.bricks_extent.y = (range->extent.y + 7) / 8; 66 | user_state.bricks_extent.z = (range->extent.z + 7) / 8; 67 | user_state.temp_brick_infos = std::make_unique>(user_state.channels.size() * user_state.bricks_extent.x * user_state.bricks_extent.y * user_state.bricks_extent.z); 68 | } 69 | 70 | extern "C" void gvox_serialize_adapter_gvox_brickmap_blit_end(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx) { 71 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 72 | std::vector bricks_heap{}; 73 | std::vector brick_headers{}; 74 | brick_headers.resize(static_cast(user_state.bricks_extent.x) * user_state.bricks_extent.y * user_state.bricks_extent.z * user_state.channels.size()); 75 | bricks_heap.reserve(user_state.brick_heap_size); 76 | for (uint32_t ci = 0; ci < user_state.channels.size(); ++ci) { 77 | for (uint32_t bzi = 0; bzi < user_state.bricks_extent.z; ++bzi) { 78 | for (uint32_t byi = 0; byi < user_state.bricks_extent.y; ++byi) { 79 | for (uint32_t bxi = 0; bxi < user_state.bricks_extent.x; ++bxi) { 80 | auto brick_index = bxi + byi * user_state.bricks_extent.x + bzi * user_state.bricks_extent.x * user_state.bricks_extent.y; 81 | auto &brick_header = brick_headers[brick_index + ci * user_state.bricks_extent.x * user_state.bricks_extent.y * user_state.bricks_extent.z]; 82 | auto brick_result = Brick{}; 83 | auto first_voxel = uint32_t{}; 84 | for (uint32_t zi = 0; zi < 8; ++zi) { 85 | for (uint32_t yi = 0; yi < 8; ++yi) { 86 | for (uint32_t xi = 0; xi < 8; ++xi) { 87 | auto out_index = xi + yi * 8 + zi * 64; 88 | auto &out_voxel = brick_result.voxels[out_index]; 89 | auto gxi = xi + bxi * 8; 90 | auto gyi = yi + byi * 8; 91 | auto gzi = zi + bzi * 8; 92 | auto in_index = gxi + gyi * user_state.range.extent.x + gzi * user_state.range.extent.x * user_state.range.extent.y; 93 | if (gxi < user_state.range.extent.x && 94 | gyi < user_state.range.extent.y && 95 | gzi < user_state.range.extent.z) { 96 | out_voxel = user_state.voxels[in_index * user_state.channels.size() + ci]; 97 | } else { 98 | out_voxel = 0u; 99 | } 100 | if (in_index == 0) { 101 | first_voxel = out_voxel; 102 | } 103 | if (!brick_header.loaded.is_loaded && out_voxel != first_voxel) { 104 | brick_header.loaded.is_loaded = true; 105 | } 106 | } 107 | } 108 | } 109 | if (brick_header.loaded.is_loaded) { 110 | brick_header.loaded.heap_index = static_cast(bricks_heap.size()); 111 | bricks_heap.push_back(brick_result); 112 | } else { 113 | auto &temp_brick = (*user_state.temp_brick_infos)[brick_index * user_state.channels.size() + ci]; 114 | brick_header.unloaded.lod_color = temp_brick.first_voxel; 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | user_state.brick_heap_size = static_cast(bricks_heap.size()); 122 | 123 | gvox_output_write(blit_ctx, user_state.offset, sizeof(user_state.brick_heap_size), &user_state.brick_heap_size); 124 | user_state.offset += sizeof(user_state.brick_heap_size); 125 | 126 | gvox_output_write(blit_ctx, user_state.offset, brick_headers.size() * sizeof(brick_headers[0]), brick_headers.data()); 127 | user_state.offset += brick_headers.size() * sizeof(brick_headers[0]); 128 | gvox_output_write(blit_ctx, user_state.offset, bricks_heap.size() * sizeof(bricks_heap[0]), bricks_heap.data()); 129 | user_state.offset += bricks_heap.size() * sizeof(bricks_heap[0]); 130 | } 131 | 132 | static void handle_region(BrickmapUserState &user_state, GvoxRegionRange const *range, auto user_func) { 133 | for (uint32_t zi = 0; zi < range->extent.z; ++zi) { 134 | for (uint32_t yi = 0; yi < range->extent.y; ++yi) { 135 | for (uint32_t xi = 0; xi < range->extent.x; ++xi) { 136 | auto const pos = GvoxOffset3D{ 137 | static_cast(xi) + range->offset.x, 138 | static_cast(yi) + range->offset.y, 139 | static_cast(zi) + range->offset.z, 140 | }; 141 | if (pos.x < user_state.range.offset.x || 142 | pos.y < user_state.range.offset.y || 143 | pos.z < user_state.range.offset.z || 144 | pos.x >= user_state.range.offset.x + static_cast(user_state.range.extent.x) || 145 | pos.y >= user_state.range.offset.y + static_cast(user_state.range.extent.y) || 146 | pos.z >= user_state.range.offset.z + static_cast(user_state.range.extent.z)) { 147 | continue; 148 | } 149 | auto output_rel_x = static_cast(pos.x - user_state.range.offset.x); 150 | auto output_rel_y = static_cast(pos.y - user_state.range.offset.y); 151 | auto output_rel_z = static_cast(pos.z - user_state.range.offset.z); 152 | for (uint32_t channel_i = 0; channel_i < user_state.channels.size(); ++channel_i) { 153 | auto output_index = static_cast(output_rel_x + output_rel_y * user_state.range.extent.x + output_rel_z * user_state.range.extent.x * user_state.range.extent.y) * user_state.channels.size() + channel_i; 154 | user_func(channel_i, output_index, pos); 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | // Serialize Driven 162 | extern "C" void gvox_serialize_adapter_gvox_brickmap_serialize_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t /* channel_flags */) { 163 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 164 | handle_region( 165 | user_state, range, 166 | [blit_ctx, &user_state](uint32_t channel_i, size_t output_index, GvoxOffset3D const &pos) { 167 | auto const sample_range = GvoxRegionRange{ 168 | .offset = pos, 169 | .extent = GvoxExtent3D{1, 1, 1}, 170 | }; 171 | auto region = gvox_load_region_range(blit_ctx, &sample_range, 1u << user_state.channels[channel_i]); 172 | auto sample = gvox_sample_region(blit_ctx, ®ion, &pos, user_state.channels[channel_i]); 173 | if (sample.is_present == 0u) { 174 | sample.data = 0u; 175 | } 176 | user_state.voxels[output_index] = sample.data; 177 | gvox_unload_region_range(blit_ctx, ®ion, &sample_range); 178 | }); 179 | } 180 | 181 | // Parse Driven 182 | extern "C" void gvox_serialize_adapter_gvox_brickmap_receive_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegion const *region) { 183 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 184 | handle_region( 185 | user_state, ®ion->range, 186 | [blit_ctx, region, &user_state](uint32_t channel_i, size_t output_index, GvoxOffset3D const &pos) { 187 | auto sample = gvox_sample_region(blit_ctx, region, &pos, user_state.channels[channel_i]); 188 | if (sample.is_present != 0u) { 189 | user_state.voxels[output_index] = sample.data; 190 | } 191 | }); 192 | } 193 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 21, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "defaults", 11 | "hidden": true, 12 | "binaryDir": "${sourceDir}/.out/${presetName}", 13 | "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", 14 | "generator": "Ninja Multi-Config", 15 | "cacheVariables": { 16 | "GVOX_ENABLE_FILE_IO": true, 17 | "GVOX_ENABLE_MULTITHREADED_ADAPTERS": true, 18 | "GVOX_ENABLE_THREADSAFETY": true, 19 | "GVOX_ENABLE_TESTS": true, 20 | "GVOX_ENABLE_ASAN": false, 21 | "GVOX_ENABLE_STATIC_ANALYSIS": false, 22 | "GVOX_BUILD_FOR_WEB": false, 23 | "VCPKG_OVERLAY_TRIPLETS": "${sourceDir}/cmake/vcpkg_triplets" 24 | } 25 | }, 26 | { 27 | "name": "clangd-support", 28 | "binaryDir": "${sourceDir}/build", 29 | "inherits": [ 30 | "defaults" 31 | ], 32 | "cacheVariables": { 33 | "CMAKE_EXPORT_COMPILE_COMMANDS": true 34 | } 35 | }, 36 | { 37 | "name": "defaults-windows", 38 | "hidden": true, 39 | "inherits": [ 40 | "defaults" 41 | ], 42 | "condition": { 43 | "type": "equals", 44 | "lhs": "${hostSystemName}", 45 | "rhs": "Windows" 46 | }, 47 | "cacheVariables": { 48 | "GVOX_TESTS_DISABLE_WINDOWS_CONSOLE": false 49 | } 50 | }, 51 | { 52 | "name": "defaults-linux", 53 | "hidden": true, 54 | "inherits": [ 55 | "defaults" 56 | ], 57 | "condition": { 58 | "type": "equals", 59 | "lhs": "${hostSystemName}", 60 | "rhs": "Linux" 61 | } 62 | }, 63 | { 64 | "name": "defaults-macos", 65 | "hidden": true, 66 | "inherits": [ 67 | "defaults" 68 | ], 69 | "condition": { 70 | "type": "equals", 71 | "lhs": "${hostSystemName}", 72 | "rhs": "Darwin" 73 | } 74 | }, 75 | { 76 | "name": "emscripten-wasm32-unknown-unknown", 77 | "displayName": "Emscripten wasm32", 78 | "inherits": [ 79 | "defaults" 80 | ], 81 | "cacheVariables": { 82 | "GVOX_ENABLE_FILE_IO": false, 83 | "GVOX_ENABLE_THREADSAFETY": false, 84 | "GVOX_ENABLE_TESTS": false, 85 | "GVOX_BUILD_FOR_WEB": true, 86 | "VCPKG_TARGET_TRIPLET": "wasm32-emscripten", 87 | "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/emscripten-llvm-unknown-unknown.cmake" 88 | } 89 | }, 90 | { 91 | "name": "wasi-wasm32-unknown-unknown", 92 | "displayName": "wasi wasm32", 93 | "inherits": [ 94 | "defaults" 95 | ], 96 | "cacheVariables": { 97 | "GVOX_ENABLE_FILE_IO": false, 98 | "GVOX_ENABLE_THREADSAFETY": false, 99 | "GVOX_ENABLE_TESTS": false, 100 | "GVOX_BUILD_FOR_WEB": true, 101 | "VCPKG_TARGET_TRIPLET": "wasm32-wasisdk", 102 | "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/wasi-llvm-unknown-unknown.cmake" 103 | } 104 | }, 105 | { 106 | "name": "clang-x86_64-windows-msvc", 107 | "displayName": "Clang x86_64 Windows (MSVC ABI)", 108 | "inherits": [ 109 | "defaults-windows" 110 | ], 111 | "cacheVariables": { 112 | "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/clang-x86_64-windows-msvc.cmake" 113 | } 114 | }, 115 | { 116 | "name": "cl-x86_64-windows-msvc", 117 | "displayName": "CL.exe x86_64 Windows (MSVC ABI)", 118 | "inherits": [ 119 | "defaults-windows" 120 | ], 121 | "cacheVariables": { 122 | "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/cl-x86_64-windows-msvc.cmake" 123 | } 124 | }, 125 | { 126 | "name": "gcc-x86_64-linux-gnu", 127 | "displayName": "G++ x86_64 Linux (GNU ABI)", 128 | "inherits": [ 129 | "defaults-linux" 130 | ], 131 | "cacheVariables": { 132 | "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/gcc-x86_64-linux-gnu.cmake" 133 | } 134 | }, 135 | { 136 | "name": "clang-x86_64-linux-gnu", 137 | "displayName": "Clang x86_64 Linux (GNU ABI)", 138 | "inherits": [ 139 | "defaults-linux" 140 | ], 141 | "cacheVariables": { 142 | "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/clang-x86_64-linux-gnu.cmake" 143 | } 144 | }, 145 | { 146 | "name": "clang-aarch64-macos-gnu", 147 | "displayName": "Clang AArch64 MacOS (GNU ABI?)", 148 | "inherits": [ 149 | "defaults-macos" 150 | ], 151 | "cacheVariables": { 152 | "VCPKG_CHAINLOAD_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/clang-aarch64-macos-gnu.cmake" 153 | } 154 | } 155 | ], 156 | "buildPresets": [ 157 | { 158 | "name": "emscripten-wasm32-unknown-unknown-relwithdebinfo", 159 | "displayName": "Emscripten wasm32 RelWithDebInfo", 160 | "configurePreset": "emscripten-wasm32-unknown-unknown", 161 | "configuration": "RelWithDebInfo" 162 | }, 163 | { 164 | "name": "emscripten-wasm32-unknown-unknown-release", 165 | "displayName": "Emscripten wasm32 Release", 166 | "configurePreset": "emscripten-wasm32-unknown-unknown", 167 | "configuration": "Release" 168 | }, 169 | { 170 | "name": "emscripten-wasm32-unknown-unknown-debug", 171 | "displayName": "Emscripten wasm32 Debug", 172 | "configurePreset": "emscripten-wasm32-unknown-unknown", 173 | "configuration": "Debug" 174 | }, 175 | { 176 | "name": "wasi-wasm32-unknown-unknown-relwithdebinfo", 177 | "displayName": "wasi wasm32 RelWithDebInfo", 178 | "configurePreset": "wasi-wasm32-unknown-unknown", 179 | "configuration": "RelWithDebInfo" 180 | }, 181 | { 182 | "name": "wasi-wasm32-unknown-unknown-release", 183 | "displayName": "wasi wasm32 Release", 184 | "configurePreset": "wasi-wasm32-unknown-unknown", 185 | "configuration": "Release" 186 | }, 187 | { 188 | "name": "wasi-wasm32-unknown-unknown-debug", 189 | "displayName": "wasi wasm32 Debug", 190 | "configurePreset": "wasi-wasm32-unknown-unknown", 191 | "configuration": "Debug" 192 | }, 193 | { 194 | "name": "clang-x86_64-windows-msvc-debug", 195 | "displayName": "Clang x86_64 Windows (MSVC ABI) Debug", 196 | "configurePreset": "clang-x86_64-windows-msvc", 197 | "configuration": "Debug" 198 | }, 199 | { 200 | "name": "clang-x86_64-windows-msvc-relwithdebinfo", 201 | "displayName": "Clang x86_64 Windows (MSVC ABI) RelWithDebInfo", 202 | "configurePreset": "clang-x86_64-windows-msvc", 203 | "configuration": "RelWithDebInfo" 204 | }, 205 | { 206 | "name": "clang-x86_64-windows-msvc-release", 207 | "displayName": "Clang x86_64 Windows (MSVC ABI) Release", 208 | "configurePreset": "clang-x86_64-windows-msvc", 209 | "configuration": "Release" 210 | }, 211 | { 212 | "name": "cl-x86_64-windows-msvc-debug", 213 | "displayName": "CL.exe x86_64 Windows (MSVC ABI) Debug", 214 | "configurePreset": "cl-x86_64-windows-msvc", 215 | "configuration": "Debug" 216 | }, 217 | { 218 | "name": "cl-x86_64-windows-msvc-relwithdebinfo", 219 | "displayName": "CL.exe x86_64 Windows (MSVC ABI) RelWithDebInfo", 220 | "configurePreset": "cl-x86_64-windows-msvc", 221 | "configuration": "RelWithDebInfo" 222 | }, 223 | { 224 | "name": "cl-x86_64-windows-msvc-release", 225 | "displayName": "CL.exe x86_64 Windows (MSVC ABI) Release", 226 | "configurePreset": "cl-x86_64-windows-msvc", 227 | "configuration": "Release" 228 | }, 229 | { 230 | "name": "gcc-x86_64-linux-gnu-debug", 231 | "displayName": "G++ x86_64 Linux (GNU ABI) Debug", 232 | "configurePreset": "gcc-x86_64-linux-gnu", 233 | "configuration": "Debug" 234 | }, 235 | { 236 | "name": "gcc-x86_64-linux-gnu-relwithdebinfo", 237 | "displayName": "G++ x86_64 Linux (GNU ABI) RelWithDebInfo", 238 | "configurePreset": "gcc-x86_64-linux-gnu", 239 | "configuration": "RelWithDebInfo" 240 | }, 241 | { 242 | "name": "gcc-x86_64-linux-gnu-release", 243 | "displayName": "G++ x86_64 Linux (GNU ABI) Release", 244 | "configurePreset": "gcc-x86_64-linux-gnu", 245 | "configuration": "Release" 246 | }, 247 | { 248 | "name": "clang-x86_64-linux-gnu-debug", 249 | "displayName": "Clang x86_64 Linux (GNU ABI) Debug", 250 | "configurePreset": "clang-x86_64-linux-gnu", 251 | "configuration": "Debug" 252 | }, 253 | { 254 | "name": "clang-x86_64-linux-gnu-relwithdebinfo", 255 | "displayName": "Clang x86_64 Linux (GNU ABI) RelWithDebInfo", 256 | "configurePreset": "clang-x86_64-linux-gnu", 257 | "configuration": "RelWithDebInfo" 258 | }, 259 | { 260 | "name": "clang-x86_64-linux-gnu-release", 261 | "displayName": "Clang x86_64 Linux (GNU ABI) Release", 262 | "configurePreset": "clang-x86_64-linux-gnu", 263 | "configuration": "Release" 264 | }, 265 | { 266 | "name": "clang-aarch64-macos-gnu-debug", 267 | "displayName": "Clang AArch64 MacOS (GNU ABI?) Debug", 268 | "configurePreset": "clang-aarch64-macos-gnu", 269 | "configuration": "Debug" 270 | }, 271 | { 272 | "name": "clang-aarch64-macos-gnu-relwithdebinfo", 273 | "displayName": "Clang AArch64 MacOS (GNU ABI?) RelWithDebInfo", 274 | "configurePreset": "clang-aarch64-macos-gnu", 275 | "configuration": "RelWithDebInfo" 276 | }, 277 | { 278 | "name": "clang-aarch64-macos-gnu-release", 279 | "displayName": "Clang AArch64 MacOS (GNU ABI?) Release", 280 | "configurePreset": "clang-aarch64-macos-gnu", 281 | "configuration": "Release" 282 | } 283 | ] 284 | } -------------------------------------------------------------------------------- /src/adapters/parse/voxlap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | struct VoxlapParseUserState { 14 | GvoxVoxlapParseAdapterConfig config{}; 15 | size_t offset{}; 16 | 17 | std::vector colors{}; 18 | std::vector is_solid{}; 19 | }; 20 | 21 | // Base 22 | extern "C" void gvox_parse_adapter_voxlap_create(GvoxAdapterContext *ctx, void const *config) { 23 | auto *user_state_ptr = malloc(sizeof(VoxlapParseUserState)); 24 | [[maybe_unused]] auto &user_state = *(new (user_state_ptr) VoxlapParseUserState()); 25 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 26 | if (config != nullptr) { 27 | user_state.config = *static_cast(config); 28 | if (user_state.config.size_x == std::numeric_limits::max()) { 29 | user_state.config.size_x = 1024; 30 | } 31 | if (user_state.config.size_y == std::numeric_limits::max()) { 32 | user_state.config.size_y = 1024; 33 | } 34 | if (user_state.config.make_solid == std::numeric_limits::max()) { 35 | user_state.config.make_solid = 1; 36 | } 37 | if (user_state.config.is_ace_of_spades == std::numeric_limits::max()) { 38 | user_state.config.is_ace_of_spades = 0; 39 | } 40 | } else { 41 | user_state.config = GvoxVoxlapParseAdapterConfig{ 42 | .size_x = 1024, 43 | .size_y = 1024, 44 | .size_z = 256, 45 | .make_solid = 1, 46 | }; 47 | } 48 | } 49 | 50 | extern "C" void gvox_parse_adapter_voxlap_destroy(GvoxAdapterContext *ctx) { 51 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 52 | user_state.~VoxlapParseUserState(); 53 | free(&user_state); 54 | } 55 | 56 | extern "C" void gvox_parse_adapter_voxlap_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 57 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 58 | auto temp_i = uint32_t{}; 59 | if (user_state.config.is_ace_of_spades == 0) { 60 | gvox_input_read(blit_ctx, user_state.offset, sizeof(temp_i), &temp_i); 61 | user_state.offset += sizeof(temp_i); 62 | if (temp_i != 0x09072000) { 63 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "A voxlap data stream must begin with a valid magic number"); 64 | return; 65 | } 66 | gvox_input_read(blit_ctx, user_state.offset, sizeof(temp_i), &temp_i); 67 | user_state.offset += sizeof(temp_i); 68 | if (temp_i != 1024) { 69 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "Second u32 not 1024"); 70 | return; 71 | } 72 | // gvox_input_read(blit_ctx, user_state.offset, sizeof(double) * 3, &ipos); // camera position 73 | user_state.offset += sizeof(double) * 3; 74 | // gvox_input_read(blit_ctx, user_state.offset, sizeof(double) * 3, &istr); // unit right vector 75 | user_state.offset += sizeof(double) * 3; 76 | // gvox_input_read(blit_ctx, user_state.offset, sizeof(double) * 3, &ihei); // unit down vector 77 | user_state.offset += sizeof(double) * 3; 78 | // gvox_input_read(blit_ctx, user_state.offset, sizeof(double) * 3, &ifor); // unit forward vector 79 | user_state.offset += sizeof(double) * 3; 80 | } 81 | uint32_t x = 0; 82 | uint32_t y = 0; 83 | uint32_t z = 0; 84 | uint32_t const voxel_n = user_state.config.size_x * user_state.config.size_y * user_state.config.size_z; 85 | user_state.colors.resize(voxel_n); 86 | user_state.is_solid.resize(voxel_n); 87 | auto setgeom = [&user_state](uint32_t x, uint32_t y, uint32_t z, bool solid) { 88 | if (z >= user_state.config.size_z) { 89 | return; 90 | } 91 | auto index = x + (user_state.config.size_y - 1 - y) * user_state.config.size_x + (user_state.config.size_z - 1 - z) * user_state.config.size_x * user_state.config.size_y; 92 | user_state.is_solid[index] = solid; 93 | }; 94 | auto setcol = [&user_state](uint32_t x, uint32_t y, uint32_t z, uint32_t color) { 95 | if (z >= user_state.config.size_z) { 96 | return; 97 | } 98 | auto index = x + (user_state.config.size_y - 1 - y) * user_state.config.size_x + (user_state.config.size_z - 1 - z) * user_state.config.size_x * user_state.config.size_y; 99 | uint32_t c = 0; 100 | c |= ((color >> 0x10) & 0xff) << 0x00; 101 | c |= ((color >> 0x08) & 0xff) << 0x08; 102 | c |= ((color >> 0x00) & 0xff) << 0x10; 103 | user_state.colors[index] = c; 104 | }; 105 | for (y = 0; y < user_state.config.size_y; ++y) { 106 | for (x = 0; x < user_state.config.size_x; ++x) { 107 | for (z = 0; z < user_state.config.size_z; ++z) { 108 | setgeom(x, y, z, user_state.config.make_solid != 0u); 109 | } 110 | z = 0; 111 | auto v = std::array{}; 112 | for (;;) { 113 | gvox_input_read(blit_ctx, user_state.offset, sizeof(v), &v); 114 | uint32_t const number_4byte_chunks = v[0]; 115 | uint32_t const top_color_start = v[1]; 116 | uint32_t const top_color_end = v[2]; 117 | uint32_t bottom_color_start = 0; 118 | uint32_t bottom_color_end = 0; 119 | uint32_t len_top = 0; 120 | uint32_t len_bottom = 0; 121 | if (user_state.config.make_solid != 0u) { 122 | for (auto i = z; i < top_color_start; ++i) { 123 | setgeom(x, y, i, false); 124 | } 125 | } 126 | auto color_offset = user_state.offset + 4; 127 | for (z = top_color_start; z <= top_color_end; ++z) { 128 | if (user_state.config.make_solid == 0u) { 129 | setgeom(x, y, z, true); 130 | } 131 | auto col = uint32_t{}; 132 | gvox_input_read(blit_ctx, color_offset, sizeof(col), &col); 133 | color_offset += 4; 134 | setcol(x, y, z, col); 135 | } 136 | len_bottom = top_color_end - top_color_start + 1; 137 | if (number_4byte_chunks == 0) { 138 | // infer ACTUAL number of 4-byte chunks from the length of the color data 139 | user_state.offset += 4 * static_cast(len_bottom + 1); 140 | break; 141 | } 142 | len_top = (number_4byte_chunks - 1) - len_bottom; 143 | user_state.offset += static_cast(v[0]) * 4; 144 | gvox_input_read(blit_ctx, user_state.offset, sizeof(v), &v); 145 | bottom_color_end = v[3]; // aka air start 146 | bottom_color_start = bottom_color_end - len_top; 147 | for (z = bottom_color_start; z < bottom_color_end; ++z) { 148 | if (user_state.config.make_solid == 0u) { 149 | setgeom(x, y, z, true); 150 | } 151 | auto col = uint32_t{}; 152 | gvox_input_read(blit_ctx, color_offset, sizeof(col), &col); 153 | color_offset += 4; 154 | setcol(x, y, z, col); 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | extern "C" void gvox_parse_adapter_voxlap_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 162 | } 163 | 164 | // General 165 | extern "C" auto gvox_parse_adapter_voxlap_query_details() -> GvoxParseAdapterDetails { 166 | return { 167 | .preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE, 168 | }; 169 | } 170 | 171 | extern "C" auto gvox_parse_adapter_voxlap_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange { 172 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 173 | return {{0, 0, 0}, {user_state.config.size_x, user_state.config.size_y, user_state.config.size_z}}; 174 | } 175 | 176 | extern "C" auto gvox_parse_adapter_voxlap_sample_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample { 177 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 178 | if (offset->x < 0 || offset->y < 0 || offset->z < 0 || 179 | static_cast(offset->x) >= user_state.config.size_x || 180 | static_cast(offset->y) >= user_state.config.size_y || 181 | static_cast(offset->z) >= user_state.config.size_z) { 182 | return {0u, 0u}; 183 | } 184 | auto voxel_index = 185 | static_cast(offset->x) + 186 | static_cast(offset->y) * user_state.config.size_x + 187 | static_cast(offset->z) * user_state.config.size_x * user_state.config.size_y; 188 | switch (channel_id) { 189 | case GVOX_CHANNEL_ID_COLOR: return {user_state.colors[voxel_index], static_cast(user_state.is_solid[voxel_index])}; 190 | case GVOX_CHANNEL_ID_MATERIAL_ID: return {static_cast(user_state.is_solid[voxel_index]), static_cast(user_state.is_solid[voxel_index])}; 191 | default: break; 192 | } 193 | return {0u, 0u}; 194 | } 195 | 196 | // Serialize Driven 197 | extern "C" auto gvox_parse_adapter_voxlap_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) -> uint32_t { 198 | return 0; 199 | } 200 | 201 | extern "C" auto gvox_parse_adapter_voxlap_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 202 | uint32_t const available_channels = GVOX_CHANNEL_BIT_COLOR | GVOX_CHANNEL_BIT_MATERIAL_ID; 203 | if ((channel_flags & ~available_channels) != 0) { 204 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 205 | } 206 | GvoxRegion const region = { 207 | .range = *range, 208 | .channels = channel_flags & available_channels, 209 | .flags = 0u, 210 | .data = nullptr, 211 | }; 212 | return region; 213 | } 214 | 215 | extern "C" void gvox_parse_adapter_voxlap_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 216 | } 217 | 218 | // Parse Driven 219 | extern "C" void gvox_parse_adapter_voxlap_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 220 | uint32_t const available_channels = GVOX_CHANNEL_BIT_COLOR | GVOX_CHANNEL_BIT_MATERIAL_ID; 221 | if ((channel_flags & ~available_channels) != 0) { 222 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 223 | } 224 | GvoxRegion const region = { 225 | .range = *range, 226 | .channels = channel_flags & available_channels, 227 | .flags = 0u, 228 | .data = nullptr, 229 | }; 230 | gvox_emit_region(blit_ctx, ®ion); 231 | } 232 | -------------------------------------------------------------------------------- /src/adapters/parse/gvox_palette.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../shared/gvox_palette.hpp" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | struct GvoxPaletteParseUserState { 14 | GvoxRegionRange range{}; 15 | uint32_t blob_size{}; 16 | uint32_t channel_flags{}; 17 | uint32_t channel_n{}; 18 | 19 | size_t offset{}; 20 | uint32_t r_nx{}; 21 | uint32_t r_ny{}; 22 | uint32_t r_nz{}; 23 | 24 | std::vector region_headers{}; 25 | std::vector buffer{}; 26 | 27 | std::array channel_indices{}; 28 | }; 29 | 30 | // Base 31 | extern "C" void gvox_parse_adapter_gvox_palette_create(GvoxAdapterContext *ctx, void const * /*unused*/) { 32 | auto *user_state_ptr = malloc(sizeof(GvoxPaletteParseUserState)); 33 | new (user_state_ptr) GvoxPaletteParseUserState(); 34 | gvox_adapter_set_user_pointer(ctx, user_state_ptr); 35 | } 36 | 37 | extern "C" void gvox_parse_adapter_gvox_palette_destroy(GvoxAdapterContext *ctx) { 38 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 39 | user_state.~GvoxPaletteParseUserState(); 40 | free(&user_state); 41 | } 42 | 43 | extern "C" void gvox_parse_adapter_gvox_palette_blit_begin(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const * /*unused*/, uint32_t /*unused*/) { 44 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 45 | 46 | uint32_t magic = 0; 47 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &magic); 48 | user_state.offset += sizeof(uint32_t); 49 | 50 | if (magic != std::bit_cast(std::array{'g', 'v', 'p', '\0'})) { 51 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_INVALID_INPUT, "parsing a gvox palette format must begin with a valid magic number"); 52 | return; 53 | } 54 | 55 | gvox_input_read(blit_ctx, user_state.offset, sizeof(GvoxRegionRange), &user_state.range); 56 | user_state.offset += sizeof(GvoxRegionRange); 57 | 58 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &user_state.blob_size); 59 | user_state.offset += sizeof(uint32_t); 60 | 61 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &user_state.channel_flags); 62 | user_state.offset += sizeof(uint32_t); 63 | 64 | gvox_input_read(blit_ctx, user_state.offset, sizeof(uint32_t), &user_state.channel_n); 65 | user_state.offset += sizeof(uint32_t); 66 | 67 | uint32_t next_channel = 0; 68 | for (uint8_t channel_i = 0; channel_i < 32; ++channel_i) { 69 | if ((user_state.channel_flags & (1u << channel_i)) != 0) { 70 | user_state.channel_indices[channel_i] = next_channel; 71 | ++next_channel; 72 | } 73 | } 74 | 75 | user_state.r_nx = (user_state.range.extent.x + REGION_SIZE - 1) / REGION_SIZE; 76 | user_state.r_ny = (user_state.range.extent.y + REGION_SIZE - 1) / REGION_SIZE; 77 | user_state.r_nz = (user_state.range.extent.z + REGION_SIZE - 1) / REGION_SIZE; 78 | 79 | user_state.region_headers.resize(static_cast(user_state.r_nx) * user_state.r_ny * user_state.r_nz * user_state.channel_n); 80 | for (auto ®ion_header : user_state.region_headers) { 81 | gvox_input_read(blit_ctx, user_state.offset, sizeof(ChannelHeader), ®ion_header); 82 | user_state.offset += sizeof(ChannelHeader); 83 | } 84 | 85 | user_state.buffer.resize(user_state.blob_size); 86 | gvox_input_read(blit_ctx, user_state.offset, user_state.blob_size, user_state.buffer.data()); 87 | } 88 | 89 | extern "C" void gvox_parse_adapter_gvox_palette_blit_end(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/) { 90 | } 91 | 92 | // General 93 | extern "C" auto gvox_parse_adapter_gvox_palette_query_details() -> GvoxParseAdapterDetails { 94 | return { 95 | .preferred_blit_mode = GVOX_BLIT_MODE_DONT_CARE, 96 | }; 97 | } 98 | 99 | extern "C" auto gvox_parse_adapter_gvox_palette_query_parsable_range(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx) -> GvoxRegionRange { 100 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 101 | return user_state.range; 102 | } 103 | 104 | extern "C" auto gvox_parse_adapter_gvox_palette_sample_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegion const * /*unused*/, GvoxOffset3D const *offset, uint32_t channel_id) -> GvoxSample { 105 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 106 | if (offset->x < user_state.range.offset.x || 107 | offset->y < user_state.range.offset.y || 108 | offset->z < user_state.range.offset.z || 109 | offset->x >= user_state.range.offset.x + static_cast(user_state.range.extent.x) || 110 | offset->y >= user_state.range.offset.y + static_cast(user_state.range.extent.y) || 111 | offset->z >= user_state.range.offset.z + static_cast(user_state.range.extent.z)) { 112 | return {0u, 0u}; 113 | } 114 | auto const xi = static_cast(offset->x - user_state.range.offset.x) / REGION_SIZE; 115 | auto const yi = static_cast(offset->y - user_state.range.offset.y) / REGION_SIZE; 116 | auto const zi = static_cast(offset->z - user_state.range.offset.z) / REGION_SIZE; 117 | auto const px = static_cast(offset->x - user_state.range.offset.x) - xi * REGION_SIZE; 118 | auto const py = static_cast(offset->y - user_state.range.offset.y) - yi * REGION_SIZE; 119 | auto const pz = static_cast(offset->z - user_state.range.offset.z) - zi * REGION_SIZE; 120 | auto r_nx = user_state.r_nx; 121 | auto r_ny = user_state.r_ny; 122 | auto &channel_header = user_state.region_headers[user_state.channel_indices[channel_id] + (xi + yi * r_nx + zi * r_nx * r_ny) * user_state.channel_n]; 123 | if (channel_header.variant_n <= 1) { 124 | return {channel_header.blob_offset, 1u}; 125 | } else { 126 | uint8_t *buffer_ptr = user_state.buffer.data() + channel_header.blob_offset; 127 | if (channel_header.variant_n > MAX_REGION_COMPRESSED_VARIANT_N) { 128 | auto const index = px + py * REGION_SIZE + pz * REGION_SIZE * REGION_SIZE; 129 | // return 0; 130 | return {*reinterpret_cast(buffer_ptr + index * sizeof(uint32_t)), 1u}; 131 | } else { 132 | auto *palette_begin = reinterpret_cast(buffer_ptr); 133 | auto const bits_per_variant = ceil_log2(channel_header.variant_n); 134 | buffer_ptr += channel_header.variant_n * sizeof(uint32_t); 135 | auto const index = px + py * REGION_SIZE + pz * REGION_SIZE * REGION_SIZE; 136 | auto const bit_index = static_cast(index) * bits_per_variant; 137 | auto const byte_index = bit_index / 8; 138 | auto const bit_offset = static_cast(bit_index - byte_index * 8); 139 | auto const mask = get_mask(bits_per_variant); 140 | #if 0 141 | // Note: This is technically UB, since I think it breaks the strict aliasing rules of C++. 142 | auto input = *reinterpret_cast(buffer_ptr + byte_index); 143 | // The "correct" solution is below. 144 | #else 145 | auto input = std::bit_cast(*reinterpret_cast *>(buffer_ptr + byte_index)); 146 | #endif 147 | auto const palette_id = (input >> bit_offset) & mask; 148 | // return palette_id; 149 | return {palette_begin[palette_id], 1u}; 150 | } 151 | } 152 | } 153 | 154 | // Serialize Driven 155 | extern "C" auto gvox_parse_adapter_gvox_palette_query_region_flags(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> uint32_t { 156 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 157 | 158 | if ((channel_flags & ~user_state.channel_flags) != 0) { 159 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 160 | return 0; 161 | } 162 | 163 | auto ax = static_cast(range->offset.x - user_state.range.offset.x) / static_cast(REGION_SIZE); 164 | auto ay = static_cast(range->offset.y - user_state.range.offset.y) / static_cast(REGION_SIZE); 165 | auto az = static_cast(range->offset.z - user_state.range.offset.z) / static_cast(REGION_SIZE); 166 | auto bx = (static_cast(range->offset.x - user_state.range.offset.x) + range->extent.x + static_cast(REGION_SIZE - 1)) / static_cast(REGION_SIZE); 167 | auto by = (static_cast(range->offset.y - user_state.range.offset.y) + range->extent.y + static_cast(REGION_SIZE - 1)) / static_cast(REGION_SIZE); 168 | auto bz = (static_cast(range->offset.z - user_state.range.offset.z) + range->extent.z + static_cast(REGION_SIZE - 1)) / static_cast(REGION_SIZE); 169 | 170 | uint32_t flags = 0; 171 | 172 | for (uint32_t channel_id = 0; channel_id < 32; ++channel_id) { 173 | if (((1u << channel_id) & channel_flags) != 0) { 174 | auto channel_index = user_state.channel_indices[channel_id]; 175 | auto &a_channel_header = user_state.region_headers[channel_index + (ax + ay * user_state.r_nx + az * user_state.r_nx * user_state.r_ny) * user_state.channel_n]; 176 | if (a_channel_header.variant_n == 1) { 177 | flags |= GVOX_REGION_FLAG_UNIFORM; 178 | for (uint32_t zi = az; zi < bz; ++zi) { 179 | for (uint32_t yi = ay; yi < by; ++yi) { 180 | for (uint32_t xi = ax; xi < bx; ++xi) { 181 | auto &b_channel_header = user_state.region_headers[channel_index + (xi + yi * user_state.r_nx + zi * user_state.r_nx * user_state.r_ny) + user_state.channel_n]; 182 | if (b_channel_header.variant_n != 1 || b_channel_header.blob_offset != a_channel_header.blob_offset) { 183 | flags = 0; 184 | break; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | } 191 | } 192 | 193 | return flags; 194 | } 195 | 196 | extern "C" auto gvox_parse_adapter_gvox_palette_load_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) -> GvoxRegion { 197 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 198 | if ((channel_flags & ~user_state.channel_flags) != 0) { 199 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 200 | } 201 | GvoxRegion const region = { 202 | .range = *range, 203 | .channels = channel_flags & user_state.channel_flags, 204 | .flags = 0u, 205 | .data = nullptr, 206 | }; 207 | return region; 208 | } 209 | 210 | extern "C" void gvox_parse_adapter_gvox_palette_unload_region(GvoxBlitContext * /*unused*/, GvoxAdapterContext * /*unused*/, GvoxRegion * /*unused*/) { 211 | } 212 | 213 | // Parse Driven 214 | extern "C" void gvox_parse_adapter_gvox_palette_parse_region(GvoxBlitContext *blit_ctx, GvoxAdapterContext *ctx, GvoxRegionRange const *range, uint32_t channel_flags) { 215 | auto &user_state = *static_cast(gvox_adapter_get_user_pointer(ctx)); 216 | if ((channel_flags & ~user_state.channel_flags) != 0) { 217 | gvox_adapter_push_error(ctx, GVOX_RESULT_ERROR_PARSE_ADAPTER_REQUESTED_CHANNEL_NOT_PRESENT, "Tried loading a region with a channel that wasn't present in the original data"); 218 | } 219 | GvoxRegion const region = { 220 | .range = *range, 221 | .channels = channel_flags & user_state.channel_flags, 222 | .flags = 0u, 223 | .data = nullptr, 224 | }; 225 | gvox_emit_region(blit_ctx, ®ion); 226 | } 227 | --------------------------------------------------------------------------------