Compare commits

...

211 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
5e0c5b9ada Fix tg3__arena_strdup to distinguish empty strings from absent strings
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/syoyo/tinygltf/sessions/445ab61b-4294-45e6-8faf-4f2fc8dfe369
2026-03-20 21:21:35 +00:00
copilot-swe-agent[bot]
0949983acc Initial plan 2026-03-20 21:15:45 +00:00
Syoyo Fujita
f7bd377a69 Update tiny_gltf_v3.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-21 05:36:13 +09:00
Syoyo Fujita
5d6984b9fd Update tiny_gltf_v3.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-21 05:35:53 +09:00
Syoyo Fujita
3331c6cee2 Update tinygltf_json.h
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-21 05:35:34 +09:00
Syoyo Fujita
2c7bf2c932 Fix fuzzer-found bugs, add libFuzzer harness for v3
Add tests/v3/fuzzer/ with libFuzzer harness covering all four parse
paths (auto-detect, JSON, GLB, float32 mode) with ASan+UBSan.

Fix two bugs found by 10+ hours of fuzzing (~23M iterations):

1. UB: (int64_t)inf in cj_parse_number when extreme exponents like
   22222222e222222 produce infinity. Add cj_dbl_to_i64() that clamps
   inf/NaN/out-of-range values before casting.

2. Null deref in tg3__parse_string when glTF array elements are not
   JSON objects (e.g. "scenes": [[3]]). Add is_object() validation
   in TG3__PARSE_ARRAY_SIMPLE and TG3__PARSE_ARRAY_IDX macros.

Verified clean: 5.8M additional runs with zero crashes after fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 04:10:25 +09:00
Syoyo Fujita
2aeac50277 Add fast float parser and benchmark float-heavy scene
Replace strtod() with Clinger's fast path in tinygltf_json.h for ~1.5x
faster JSON float parsing. The new parser accumulates all digits into a
uint64 mantissa and uses exact power-of-10 tables for conversion,
avoiding locale-dependent strtod for ~99% of JSON float values.

Add optional float32 parse mode (parse_float32 option) that parses JSON
floats at single precision — fewer significant digits needed, wider fast
path range. Breaks strict double-precision conformance but sufficient
for glTF data which is typically single-precision.

Benchmark additions:
- gen_synthetic: add float_heavy preset (~500MB ASCII float JSON)
- bench_v3: add --float32 flag for float32 parse mode benchmarking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 09:00:30 +09:00
Syoyo Fujita
78f4a5cfe8 Add tinygltf v3 single-header C API (tiny_gltf_v3.h)
Ground-up C-centric rewrite of tinygltf with pure C POD structs,
arena-based memory management, structured error reporting, streaming
callbacks, and no STL dependency in the public API. Uses tinygltf_json.h
as the sole JSON backend.

Includes complete parser (JSON + GLB), writer (JSON + GLB), streaming
writer, C++ RAII wrappers, and C++20 coroutine facade. Verified with
Cube.gltf and Fox.glb parse/write round-trips.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 03:31:05 +09:00
Syoyo Fujita
aa63297061 Merge branch 'release' of github.com:syoyo/tinygltf into release 2026-03-19 13:39:50 +09:00
Syoyo Fujita
7163d5ab17 ignore build/ 2026-03-19 13:39:37 +09:00
Syoyo Fujita
f9397d296d Merge pull request #535 from syoyo/copilot/optimize-json-parser
Add tinygltf_json.h: fast custom JSON parser with optional SIMD acceleration
2026-03-19 09:29:56 +09:00
copilot-swe-agent[bot]
c4e4155bf7 Super final review: fix cj_unescape_string data-loss, escape-scan infinite loop, operator[] destructor
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-18 21:44:18 +00:00
copilot-swe-agent[bot]
5dfa17d14b Final review: fix stray *out_len=len UB, add get<T> primary template for size_t
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-18 20:16:01 +00:00
Syoyo Fujita
5b87beb373 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:39:27 +09:00
Syoyo Fujita
0ab7e74933 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:39:13 +09:00
Syoyo Fujita
247cb388a0 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:38:55 +09:00
Syoyo Fujita
eb087e80e7 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:38:40 +09:00
Syoyo Fujita
690585fa73 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:38:28 +09:00
Syoyo Fujita
73d309ebfa Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:38:10 +09:00
Syoyo Fujita
4d16d528a5 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:37:50 +09:00
Syoyo Fujita
229f2b8c88 Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:37:37 +09:00
Syoyo Fujita
ad531900cb Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-19 02:37:10 +09:00
copilot-swe-agent[bot]
9da2046cba Mitochondria-level review: fix NULL ptr + nonzero length → serializer overread (6 sites)
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-18 04:34:13 +00:00
copilot-swe-agent[bot]
ed13b0422a Deepest-ever review: NaN/Inf->null, operator[] null key, copy_from_ arr_size_ tracking
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-18 04:10:06 +00:00
copilot-swe-agent[bot]
1dfcb11442 Ultra deep final review: 6 correctness/safety fixes in tinygltf_json.h
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-18 03:32:36 +00:00
copilot-swe-agent[bot]
a2b55f008e Fix security/correctness issues from thorough code review of tinygltf_json.h
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-17 23:17:24 +00:00
copilot-swe-agent[bot]
fdf528f9aa Make C++ exceptions optional in tinygltf_json.h (default off)
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-17 19:33:02 +00:00
copilot-swe-agent[bot]
ebcd8cc4ee Replace recursive parser with iterative loop using CJ_MAX_ITER explicit frame stack
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-17 19:24:38 +00:00
copilot-swe-agent[bot]
f6c71cf88b Security fixes: null key guard, allocation overflow protection, parser depth limit
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-17 16:02:25 +00:00
copilot-swe-agent[bot]
5aaa3e4daf Remove accidental test artifact files and update .gitignore
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-17 15:52:12 +00:00
copilot-swe-agent[bot]
1117aa7191 Add tinygltf_json.h: fast custom JSON parser with optional SIMD support
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-17 15:49:46 +00:00
copilot-swe-agent[bot]
bdba4dfb4c Initial plan 2026-03-17 15:15:01 +00:00
Syoyo Fujita
e379d0d60c Merge pull request #533 from syoyo/copilot/remove-appveyor-ci-config
Remove AppVeyor CI config and badge from README
2026-03-05 13:29:40 +09:00
copilot-swe-agent[bot]
659de95977 Remove AppVeyor CI config and badge from README
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-03-05 04:27:49 +00:00
copilot-swe-agent[bot]
b1a7736249 Initial plan 2026-03-05 04:27:04 +00:00
Syoyo Fujita
9ab0d0d5f7 Merge pull request #532 from AnisB/remove_std_namespace
Removing problematic using namespace in a header file
2026-03-03 02:59:00 +09:00
benyo-razer
fca5da1b37 Removing problematic using namespace in a header file 2026-03-02 12:21:07 +01:00
Syoyo Fujita
bdc37385f1 Merge pull request #531 from syoyo/copilot/update-ci-workflow-comprehensive-builds
Update Linux Clang CI job to use Clang 21
2026-02-06 01:39:55 +09:00
Syoyo Fujita
797bf0e023 Use CMake and ctest for Linux and macOS native builds
Convert manual g++/clang++ builds to CMake for consistency with
Windows builds:
- Linux x64 (GCC)
- Linux ARM64 (GCC)
- macOS ARM64 Apple Silicon (Clang)

All native builds now use:
- cmake -B build -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON
- cmake --build build
- ctest --test-dir build --output-on-failure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 00:52:59 +09:00
Syoyo Fujita
10ac914244 Update Linux Clang job to use Clang 21
- Install Clang 21 from LLVM apt repository
- Use CMake with clang-21/clang++-21 compilers
- Run tests with ctest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 00:24:55 +09:00
Syoyo Fujita
dc6dddac98 Merge pull request #529 from syoyo/copilot/update-ci-workflow-comprehensive-builds
Add comprehensive multi-platform CI workflow with 15 build configurations
2026-02-05 05:42:31 +09:00
Syoyo Fujita
b548191e41 Use CMake and ctest for RapidJSON backend build
Convert RapidJSON backend job from manual g++ builds to CMake,
enabling ctest for running tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 05:35:46 +09:00
Syoyo Fujita
17287c7fcf Add ctest to CMake-based builds
Run ctest after build for:
- Windows x64 MSVC
- Windows x86 MSVC
- Windows MinGW MSYS2
- Linux Header-Only Mode

Cross-compile builds (Windows ARM64, Linux→Windows MinGW) are excluded
since tests cannot run on the host.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 05:19:22 +09:00
Syoyo Fujita
6c948d5bc3 Remove macOS Intel job from CI
macOS Intel runners are being deprecated. Keep only the ARM64
Apple Silicon job for macOS coverage.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 05:08:09 +09:00
Syoyo Fujita
d4a4a1b27a Fix CI: update macOS runner and remove Windows unit tests
- Change macos-13 to macos-15-large for Intel x64 (macos-13 is retired)
- Remove Windows MSVC unit tests (they have path-related issues and
  the existing c-cpp.yml workflow doesn't run tests on Windows either)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 04:54:40 +09:00
Syoyo Fujita
3d5453ecd0 Fix Windows MSVC unit tests build by setting up developer environment
The `cl` compiler was not in PATH because the Visual Studio developer
environment needs to be set up before calling MSVC tools directly.
Added `ilammy/msvc-dev-cmd@v1` action to configure the environment.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 04:54:40 +09:00
copilot-swe-agent[bot]
52a453120b Add security: restrict GITHUB_TOKEN permissions to read-only
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-02-05 04:54:40 +09:00
copilot-swe-agent[bot]
fc6d78a1b6 Add comprehensive CI workflow with multi-platform builds and tests
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-02-05 04:54:40 +09:00
copilot-swe-agent[bot]
ae0bac486c Initial plan 2026-02-05 04:54:40 +09:00
Syoyo Fujita
b19e665747 Merge pull request #530 from syoyo/add-cmake-test-target
Add CMake test target and fix Windows test failure
2026-02-05 04:52:23 +09:00
Syoyo Fujita
40f6c2b875 Add CMake test target and fix Windows test failure
- Add TINYGLTF_BUILD_TESTS option to build unit tests via CMake
- Test runs from tests/ directory so relative paths work correctly
- Fix Windows file sharing violation in images-as-is test by closing
  fstream before stbi_load attempts to open the same file

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 04:45:27 +09:00
Syoyo Fujita
e8c70dff1d Merge pull request #528 from syoyo/copilot/add-copilot-review-instructions
Add Copilot review instructions for code quality and security checks
2026-02-03 08:55:23 +09:00
copilot-swe-agent[bot]
1dc37f76ea Add Copilot review instructions for tinygltf repository
Co-authored-by: syoyo <18676+syoyo@users.noreply.github.com>
2026-02-02 23:48:15 +00:00
copilot-swe-agent[bot]
8da66b8ca1 Initial plan 2026-02-02 23:46:46 +00:00
Syoyo Fujita
81bd50c106 Merge branch 'release' of github.com:syoyo/tinygltf into release 2025-11-02 10:11:11 +09:00
Syoyo Fujita
6d8bba0d8a Update the usage code: https://github.com/syoyo/tinygltf/pull/524 2025-11-02 10:10:11 +09:00
Syoyo Fujita
2aa77e5d0a Merge pull request #525 from nyalldawson/performance
Minor performance fixes
2025-11-02 10:08:23 +09:00
Nyall Dawson
1fac6234d9 Fix some 'use of auto that causes a copy' warnings 2025-10-31 08:44:42 +10:00
Nyall Dawson
bcd666fbd4 Fix some variable copied when could be moved warnings 2025-10-31 08:44:26 +10:00
Syoyo Fujita
37250b3470 Merge pull request #517 from nepp95/release
Removed TINYGLTF_USE_CPP14 option since it is unused
2025-05-20 06:31:38 +09:00
Niels Eppenhof
7385235e29 Removed TINYGLTF_USE_CPP14 option since it is unused 2025-05-19 14:58:41 +02:00
Syoyo Fujita
3564b48760 Merge pull request #516 from DrQuackeroo/c24695-fix
Initialize Accessor::Sparse members to default values
2025-05-06 09:14:08 +09:00
Sammy Newhide
2ad433b68f Revert 1b517f2 by adding default constructor for Accessor 2025-05-05 17:04:52 -07:00
Sammy Newhide
1b517f2b23 Remove explicit default constructor for Accessor
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-05 16:43:20 -07:00
Sammy Newhide
bd7255e095 Initialize Accessor::Sparse members to default values 2025-05-05 15:20:30 -07:00
Syoyo Fujita
a5e653e46c Merge pull request #512 from ctrlaltf2/oob-fix
Add bounds check to images loaded from bufferviews
2025-01-22 22:45:07 +09:00
ctrlaltf2
d530cd410b Add bounds check to images loaded from bufferviews 2025-01-20 23:43:07 -05:00
Syoyo Fujita
1831424c71 Merge pull request #509 from NoirMorilec/fix-no-fs
Added NO_FS definition for std::ofstream usage
2024-12-30 22:11:09 +09:00
Leonid
5e008af65d Added NO_FS definition for std::ofstream usage 2024-12-30 03:12:20 +08:00
Syoyo Fujita
fbff1f45b5 Merge pull request #507 from thearchivalone/release
Documentation: Submodule hint added
2024-12-21 19:59:42 +09:00
Brad
d950e7cd9b Documentation: Submodule hint added 2024-12-20 17:49:51 -06:00
Syoyo Fujita
116d0030f9 Merge pull request #504 from nim65s/vendor 2024-10-16 23:27:42 +09:00
Syoyo Fujita
ff972dcf1b Merge pull request #503 from nim65s/release 2024-10-16 23:26:53 +09:00
Guilhem Saurel
8bec431699 CMake: fix export install dir 2024-10-16 15:53:48 +02:00
Guilhem Saurel
21485496b1 CMake: allow opt-out of installing vendored headers 2024-10-16 14:58:42 +02:00
Syoyo Fujita
fda7422022 Merge pull request #501 from msklywenn/release
Fix Animation extensions being loaded in place of Sampler extensions
2024-08-08 21:47:56 +09:00
Daniel Borges
decfabd67e Merge branch 'syoyo:release' into release 2024-08-08 10:25:11 +02:00
Syoyo Fujita
10b23b6af2 Merge pull request #496 from ptc-tgamper/bug/issue-495
Allow WriteImageDataFunction() callback to be called with empty images
2024-07-26 21:44:16 +09:00
Thomas Gamper
fe3cfbe996 fixes #495
Fix issues that block custom image loaders and writers to deal with empty images
2024-07-23 11:45:54 +02:00
Daniel Borges
3b73caa8e8 fixed ParseAnimation loading animation's extensions into sampler instead of sample's extensions. 2024-07-19 12:34:16 +02:00
Syoyo Fujita
fea6786129 Merge pull request #493 from ptc-tgamper/bug/model_clear_on_load
Properly clear the model before loading
2024-07-06 02:43:52 +09:00
Thomas Gamper
fb58f88a4e Properly clear the model before loading 2024-07-05 08:57:36 +02:00
Syoyo Fujita
143ff45b61 Update README.md 2024-07-04 03:01:46 +09:00
Syoyo Fujita
cfbec35dc7 Merge pull request #492 from danwillm/inverse-bind-matrix
Make inverseBindMatrices optional
2024-07-04 02:59:35 +09:00
danwillm
4ad8c82c9e Add test for inverse bind matrices being optional 2024-07-01 22:32:17 +01:00
danwillm
2e7ba45a6c Make inverseBindMatrices optional 2024-07-01 18:31:00 +01:00
Syoyo Fujita
cf9767668a bump minor version. 2024-06-28 21:30:43 +09:00
Syoyo Fujita
8a269aa5e9 Merge pull request #491 from ptc-tgamper/bug/image_saving
Fix images not being saved due to missing filesystem callbacks
2024-06-28 21:24:53 +09:00
Thomas Gamper
38614763e9 fixes #487
Support image as_is flag in loading and saving
2024-06-28 12:17:38 +02:00
Thomas Gamper
3245906248 fixes #473
tiny_gltf.h - explicitly pass filesystem callbacks to image related functions
tester.cc - add respective test case, fix image uri test case
2024-06-25 15:12:30 +02:00
Syoyo Fujita
847df8456a Merge pull request #489 from SeanCurtis-TRI/PR_callback_as_function
Update typedefs of C-style function pointers to std::function
2024-06-12 04:01:37 +09:00
Sean Curtis
6482c08cf7 Remove asserts 2024-06-10 14:18:31 -07:00
Sean Curtis
e08df72575 Update typedefs of C-style function pointers to std::function
This allows for the callback to maintain their own state (without recourse
to globals).

In addition, added some incidental clean up:

  - URICallback, passed by pointer, is now asserted to be non-null before
    accessing.
  - FSCallbacks are validated when they are set (in debug builds).
2024-06-06 07:45:01 -07:00
Syoyo Fujita
f03fe26579 Merge pull request #486 from pmcgvr/release
Fix stripping of slashes from paths
2024-05-24 02:49:22 +09:00
Syoyo Fujita
e54660fbf9 Merge pull request #485 from bwrsandman/patch-1
msvc 32bit: Fix C4244 warning
2024-05-21 04:11:31 +09:00
Patrick Mc Gartoll
1bdd404c04 Fix stripping of slashes from paths 2024-05-16 18:11:26 -07:00
Sandy
2191085580 msvc 32bit: Fix C4244 warning
On 32 bit msvc compilers with warnings on, there are C4244 warnings about  from 'std::streamoff' to size_t to vector::size_type
2024-05-10 08:47:23 -04:00
Syoyo Fujita
cde43ef668 Merge pull request #482 from jam3sward/fix-c4018-warnings-msvc-win32
Fix C4018 warnings in MSVC on WIN32
2024-03-27 03:43:42 +09:00
jamesvert
e3f9a7d8b3 Resolve overload ambiguity in VS2015 (version 14.0) 2024-03-26 11:32:43 +00:00
jamesvert
f57d18ad74 Fix C4018 warnings in MSVC on WIN32 2024-03-26 11:06:16 +00:00
Syoyo Fujita
ed3d1ec2f5 Merge pull request #481 from jam3sward/fix-c4267-warnings-vs-win32
Fix C4267 warnings in Visual Studio on WIN32
2024-03-26 05:23:03 +09:00
James Ward
9b4e1eae9e Fix C4267 warnings in Visual Studio on WIN32 2024-03-25 17:50:33 +00:00
Syoyo Fujita
cbc8e1bea6 Merge pull request #479 from The0Dev/fix_wopen_ronly
Added the pmode argument to _wopen to fix the access permission on MinGW
2024-03-26 02:36:27 +09:00
Syoyo Fujita
212de904ca Merge pull request #480 from ptc-tgamper/bug/msft_lod_extension_used
Bug/msft lod extension used
2024-03-26 02:35:51 +09:00
Thomas Gamper
1f5b8f8b8c tester.cc - extend MSFT_lod test 2024-03-25 17:01:08 +01:00
Thomas Gamper
b274b34972 tiny_gltf.h - register MSFT_lod with the model's used extensions 2024-03-25 17:00:41 +01:00
TheDev
22dfeab315 Added the pmode argument to _wopen to fix the access permission on MinGW 2024-03-25 18:21:20 +03:00
Syoyo Fujita
b132612307 Merge pull request #475 from jam3sward/issue-474
M_PI was not defined by <cmath>
2024-03-20 21:11:56 +09:00
James Ward
50d90c91ac M_PI was not defined by <cmath> 2024-03-19 19:06:19 +00:00
Syoyo Fujita
4bfc1fc180 Merge pull request #471 from ptc-tgamper/ftr/msft_lods
[Feature] Add basic support for MSFT_lod extension
2024-02-06 23:22:38 +09:00
Thomas Gamper
e0cc45e88d tester.cc - add test case for the crash fix in KHR_audio node serialization 2024-02-06 14:48:58 +01:00
Thomas Gamper
c3bbe97a9e tester.cc - add MSFT_lods test case 2024-02-05 17:03:16 +01:00
Thomas Gamper
f1bdf43e15 tiny_gltf.h - add/remove MSFT_extension as required 2024-02-05 16:50:46 +01:00
Thomas Gamper
a42263bdba tiny_gltf.h - parse node and material lods 2024-02-05 15:42:49 +01:00
Syoyo Fujita
4fea26f6c8 Allow zero-sized BIN chunk. Fixes #440 2024-01-24 05:43:27 +09:00
Syoyo Fujita
c5641f2c22 Deprecate Travis CI and remove Travis CI scripts. Fixes #439 2023-12-11 21:00:35 +09:00
Syoyo Fujita
6782f887bb Merge pull request #467 from rhiskey/release
Small security and overflow patch
2023-12-05 06:42:19 +09:00
rhiskey
8fdeca146e Update stb_image_write.h
Provided `sizeof(buffer)` in `sptintf`
2023-12-05 00:28:18 +03:00
rhiskey
7fd75df70e Revert back stb_image_write.h to original code 2023-12-05 00:21:41 +03:00
rhiskey
77238cf23c Update stb_image_write.h
Fixed case when  `__STDC_LIB_EXT1__ ` is not defined - for Linux, etc. According to the C99 standart @syoyo
2023-12-05 00:15:50 +03:00
rhiskey
8acf861db7 Update tiny_gltf.h
Removed `#undef` 
and used the @syoyo method:

https://github.com/syoyo/tinygltf/pull/467#issuecomment-1838703699
2023-12-04 17:11:59 +03:00
rhiskey
03b3a31e02 Update tiny_gltf.h
Fixed `Windows.h` MINMAX error and reverted to original numeric limits of type `uint32_t`
2023-12-04 16:57:36 +03:00
rhiskey
30ec815748 Merge branch 'syoyo:release' into release 2023-12-04 16:55:16 +03:00
Syoyo Fujita
8387fdbd50 Fix possible nullptr dereferencing.
Add missing `return false`
2023-12-04 22:50:49 +09:00
rhiskey
32198f757f Update stb_image_write.h
Securing printing output via `sprintf_s` instead `sprintf`.
2023-12-04 14:22:14 +03:00
rhiskey
1c6f6efafc Update tiny_gltf.h
Fix max size of `header_and_json_size` limit.
In case of 4GB will check ` sizeof(uint64_t)` insted deprecated max
2023-12-04 14:21:06 +03:00
Syoyo Fujita
d32f1fb2fb Merge pull request #465 from ptc-tgamper/bug/issue464
Fix Empty scenes are wrongly serialized as null Issue #464
2023-11-23 23:28:39 +09:00
Thomas Gamper
3203e1985e Fix #464
tinygltf.h - serialize empty scenes as empty json objects; tester.cc - ad respective test case, bring test cases in correct order, tag test case for light index with correct issue number and fix it to compare to deserialozed model
2023-11-23 15:14:46 +01:00
Syoyo Fujita
211f86e3f5 Merge pull request #463 from ptc-tgamper/bug/issue458
Fix Light reference in the node issue #457
2023-11-23 22:19:05 +09:00
Thomas Gamper
afcfb57898 fix #457
tiny_gltf.h - access correct json object when serializing a light, this fixes an assert and causes us to serialze the light index properly; tester.cc - add respective testcase
2023-11-23 14:13:12 +01:00
Syoyo Fujita
b6e2398e1d Merge pull request #462 from ptc-tgamper/bug/issue457
Fix Empty nodes are wrongly serialized as null Issue #457
2023-11-23 21:52:47 +09:00
Thomas Gamper
d4ea67cae1 fix #457
tiny_gltf.h - make sure to serialize null node as empty object; tester.cc - add respective test case
2023-11-23 11:59:18 +01:00
Syoyo Fujita
f32475c952 Merge pull request #460 from ptc-tgamper/bug/issue459
Fix Default constructed Material has wrong emissiveFactor #459
2023-11-23 02:21:28 +09:00
Thomas Gamper
1f42c963e6 fix #459
tiny_gltf.h - use member initialization
2023-11-22 15:59:13 +01:00
Thomas Gamper
fd6c7855e7 fix #459
tiny_gltf.h - properly initialise emissiveFactor; tests/tester.cc - add test case
2023-11-22 14:17:46 +01:00
Syoyo Fujita
5e8a7fd602 Merge pull request #456 from haroonq/patch-1
Allow BufferView indices to be unspecified.
2023-10-10 21:24:51 +09:00
haroonq
8098a9e8ed Allow BufferView indices to be unspecified.
Allow BufferView indices for element array buffers to be unspecified to support some extensions.

Note that this is similar to how invalid array buffers are handled in order to support, for example, sparse morph targets.
2023-10-09 10:30:25 +00:00
Syoyo Fujita
e0b393c695 Merge pull request #455 from nyalldawson/fix_message
Fix incorrect component type shown in warning message
2023-09-12 20:28:35 +09:00
Nyall Dawson
c35819f0b7 Fix incorrect component type shown in warning message 2023-09-12 08:46:14 +10:00
Syoyo Fujita
4b9cfc8c1e Remove unused code. Fixes #454 2023-09-07 22:03:46 +09:00
Syoyo Fujita
c40c9c223e Merge pull request #453 from nyalldawson/fix_draco
Fix build with draco
2023-09-07 21:35:55 +09:00
Nyall Dawson
0067b4d941 Fix build with draco 2023-09-07 08:20:28 +10:00
Syoyo Fujita
35735bb686 Merge pull request #452 from nyalldawson/permissive_align
Relax bin chunk end alignment check in permissive mode
2023-09-07 06:46:11 +09:00
Nyall Dawson
4d119d7268 Relax bin chunk end alignment check in permissive mode 2023-09-07 07:08:26 +10:00
Syoyo Fujita
fe6a18269f Merge pull request #450 from nyalldawson/fix_win
Fix msvc build -- STRICT is a msvc macro name
2023-09-07 05:59:12 +09:00
Nyall Dawson
bbc1eaeecf Fix msvc build -- STRICT is a msvc macro name 2023-09-07 06:28:37 +10:00
Syoyo Fujita
62cc92566e Merge pull request #451 from nyalldawson/mingw
Add mingw msys2 workflow
2023-09-06 20:55:14 +09:00
Nyall Dawson
b2aca1ecef Add mingw msys2 workflow 2023-09-06 08:26:34 +10:00
Nyall Dawson
5a7b8278cd Fix warning when building without draco support 2023-09-03 09:04:56 +10:00
Syoyo Fujita
3d445cc65d Merge pull request #449 from emimvi/consistent_byteOffset
Always use size_t for byte offsets
2023-09-03 02:20:35 +09:00
Syoyo Fujita
51530ee500 Merge pull request #447 from nyalldawson/draco_fix
Handle the situation where the recorded component type does not match the required type for the actual number of stored points
2023-09-03 02:17:12 +09:00
emimvi
759976e087 Consistently use size_t for all byteOffset's 2023-09-02 09:39:53 +02:00
Nyall Dawson
6e3d666cf3 When in permissive mode, handle the situation where the
recorded component type does not match the required type
for the actual number of stored points

This situation arises when decoding certain malformed files, most
notably it's seen in glb tiles from Google Earth's 3d tileset.

It's a port of the workaround used by Cesium native here:

d9172461e2/CesiumGltfReader/src/decodeDraco.cpp (L101)
2023-09-02 10:16:04 +10:00
emimvi
bf7120f8a0 Serialize byteOffset as size_t, avoiding cast
Fixes silently writing an overflowed int in the output file.
2023-09-02 00:11:41 +02:00
Syoyo Fujita
acf1e8a2b1 Merge pull request #445 from nyalldawson/permissive
Be tolerant when encountering emissiveFactor with array length 4
2023-09-01 23:10:29 +09:00
Nyall Dawson
8c85d5e387 Add method to set parsing strictness 2023-08-28 12:56:09 +10:00
Nyall Dawson
02e8b8da1e Raise a warning when encountering emissiveFactor with array length
of 4 instead of aborting the model loading
2023-08-28 12:46:44 +10:00
Syoyo Fujita
ddc76f7724 Merge pull request #446 from nyalldawson/fix_error
Fix misleading error message
2023-08-24 22:11:50 +09:00
Nyall Dawson
8e9aadf569 Fix misleading error message
Avoids a confusing "Must have 4 bytes or more bytes, but got 4."
error.
2023-08-24 11:48:09 +10:00
Syoyo Fujita
0eaa23fbfc Merge pull request #442 from turanszkij/release-1
added project link to readme
2023-08-07 18:32:30 +09:00
Turánszki János
2a5dc852cc added project link to readme 2023-08-07 06:59:35 +02:00
Syoyo Fujita
f51243da48 Merge pull request #433 from dyollb/modernize
Modernize
2023-07-11 19:43:53 +09:00
Syoyo Fujita
a080377e6f Merge pull request #436 from dimitri-tdg/bugfix_extensions_primitive
Fix issue when serializing extensions of a primitive
2023-07-10 23:30:40 +09:00
KUDELSKI Dimitri
69d75573f5 Fix issue when serializing extensions of a primitive 2023-07-10 15:13:09 +02:00
Syoyo Fujita
aaf631c984 Merge pull request #435 from RE-Kovalev/release
MinGW _wfopen_s fix
2023-07-05 18:41:22 +09:00
R.E. Kovalev
112e3537ff MinGW _wfopen_s fix 2023-07-05 09:11:57 +03:00
Bryn Lloyd
cb6a707014 cppcoreguidelines-prefer-member-initializer 2023-06-21 22:30:14 +02:00
Bryn Lloyd
3e98ac4564 modernize-use-equals-default 2023-06-21 22:15:49 +02:00
Bryn Lloyd
c704d73bd0 readability-redundant-string-init 2023-06-21 18:42:24 +02:00
Bryn Lloyd
a64f4b4442 modernize-use-default-member-init 2023-06-21 18:40:18 +02:00
Bryn Lloyd
85b4322ade minor cleanup of cmakelists 2023-06-21 18:26:43 +02:00
Syoyo Fujita
7a570c88d9 Initialize light and emitter members. Fixes #431
Include `light` and `emitter` comparison in Node::operator==()
2023-06-19 21:52:13 +09:00
Syoyo Fujita
e12e0a9392 Merge pull request #429 from AdamFull/release
KHR_audio parsing support (ASCII only)
2023-06-13 21:40:45 +09:00
Baranob_Ilya
879cb473a3 Added parsing and serializing of audio emitters for scene (global sources) 2023-06-12 13:35:05 +04:00
Baranob_Ilya
c9657be1de Forgot to add KHR_audio to used extensions 2023-06-12 12:34:34 +04:00
Baranob_Ilya
95bbf15ce2 Update .gitignore 2023-06-12 12:30:04 +04:00
Baranob_Ilya
78864c8d3a Added KHR_audio extension. Parsing and serialization.
Added KHR_audio extension parsing and serializing. Only for ascii
2023-06-12 10:43:52 +04:00
Syoyo Fujita
14c86324d7 Merge pull request #426 from agnat/serialize_light_refs
KHR_lights_punctual: Serialize node light refs
2023-06-09 05:03:01 +09:00
David Siegel
8d5d0b34be clean up 2023-06-07 15:35:35 +02:00
David Siegel
a1a34cb54d fix syntax error 2023-06-07 15:30:02 +02:00
David Siegel
cfe64fb6c8 KHR_lights_punctual: Serialize node light refs
This is surprisingly involved:

1. Add non-const json iterators, FindMember(…) and GetValue(…)
2. Add json utilities IsEmpty(…) and Erase(…)
3. Serialize the property and clean up
2023-06-07 15:18:38 +02:00
Syoyo Fujita
c201efb998 Merge pull request #422 from agnat/fix/add_missing_extras_and_extensions
Add missing extras and extensions fields
2023-06-07 02:47:59 +09:00
Syoyo Fujita
b88e9cc52a Merge pull request #423 from agnat/finish_KHR_lights_puncutal
KHR_lights_punctual: parse light source references from scene nodes
2023-06-06 23:12:50 +09:00
Syoyo Fujita
9417144f48 Merge pull request #424 from agnat/rewrite_ForEachInArray
get rid of std::function
2023-06-06 23:11:12 +09:00
David Siegel
07616e8190 refactor extension and extra serialization
Add missing serialization:

accessor.extensions
accessor.sparse.extensions
accessor.sparse.extras
accessor.sparse.indices.extensions
accessor.sparse.indices.extras
accessor.sparse.values.extensions
accessor.sparse.values.extras
animation.channel.target.extras
animation.sampler.extensions
buffer.extensions
bufferView.extensions
sampler.extensions
camera.orthographic.extensions
camera.perspective.extensions
skin.extras
skin.extensions
2023-06-06 15:40:29 +02:00
David Siegel
bec8a6d54f rewrite ForEachInArray as a simple C++11 function
no need for std::function here. A free function with an unspecified callback will do nicely.
2023-06-06 15:36:07 +02:00
David Siegel
157063fa1b fix: add missing nullptr check 2023-06-06 15:31:58 +02:00
David Siegel
c164878d0f parse light source references 2023-06-06 06:18:14 +02:00
David Siegel
d852f50d49 Add missing extras and extensions fields
Handle extras and extensions in nested “sub-objects”:

animation.channel.target.extras
accessor.sparse.extras
accessor.sparse.extensions
accessor.sparse.indices.extras
accessor.sparse.indices.extensions
accessor.sparse.values.extras
accessor.sparse.values.extensions
2023-06-06 00:07:37 +02:00
David Siegel
22cafa1032 remove duplicate code
use a function to parse extras and extensions
2023-06-06 00:07:37 +02:00
David Siegel
47208b234d fix warnings: unused variable 2023-06-06 00:07:37 +02:00
Syoyo Fujita
5a6df34d99 Simplify version comment in tiny_gltf.h
Remove wuffs code(which was accidently adde to `release` branch)
2023-06-04 19:07:00 +09:00
Syoyo Fujita
147a00a601 Prevent duplicated key generation when serializing lights + RapidJSON backend. Fixes #420 2023-06-04 05:45:24 +09:00
Syoyo Fujita
350c296802 Merge pull request #418 from agnat/fix_get_file_size_bug
[Bugfix] Actually invoke the user-supplied function instead of subtracting from a pointer...
2023-04-26 19:28:58 +09:00
David Siegel
cc93e1fd25 Fix: Actually invoke the user-supplied function 2023-04-26 12:13:41 +02:00
Syoyo Fujita
59cc44ad4f Merge pull request #417 from syoyo/filesize-check
Fix to #416
2023-04-23 23:15:07 +09:00
Syoyo Fujita
1a5046e06b Fix MSVC compile failure on AppVeyor CI. 2023-04-23 23:08:41 +09:00
Syoyo Fujita
877d856e71 Format error message.
Add regression test of issue-416.
2023-04-23 21:47:31 +09:00
Syoyo Fujita
b534b6b0d8 Fix syntax. 2023-04-23 21:40:23 +09:00
Syoyo Fujita
ecfd37dee2 - Add GetFileSizeInBytes Filesystem Callback
- Add feature to limit file size for external resources(images, buffers)
- Use strlen to correctly retrieve a string from a string which contains multiple null-characters.
- Return fail when opening a directory(Posix only). Fixes #416
2023-04-23 21:31:30 +09:00
Syoyo Fujita
5c06b7d03b Merge pull request #415 from louwaque/release
Fix serialization of AnimationChannel::target_node when it is zero
2023-04-19 05:17:06 +09:00
Loïc Escales
a75355b018 Fix serialization of AnimationChannel::target_node when it is zero 2023-04-18 21:03:39 +02:00
Syoyo Fujita
a977f7a16f Merge pull request #412 from agnat/add_char_pointer_ctor
Fix #411 by adding a Value(const char *) constructor.
2023-04-10 19:11:35 +09:00
Syoyo Fujita
5a6c55870e Deprecate ubuntu-18.04 image and update CI build configuration. 2023-04-10 18:51:29 +09:00
David Siegel
49caa65177 Fix #411 by adding a Value(const char *) constructor.
Avoid implicit conversion of pointers to bool. Closes #411.
2023-04-08 23:44:53 +02:00
Syoyo Fujita
d71c6f61f3 Merge pull request #409 from NeilBickford-NV/nbickford/update-stb-image
Updates stb_image and stb_image_write
2023-03-30 18:13:12 +09:00
Neil Bickford
b5d27fd151 stb_image: Apply https://github.com/nothings/stb/pull/1443 2023-03-29 12:00:24 -07:00
Neil Bickford
af4456ba68 stb_image: Apply https://github.com/nothings/stb/pull/1454 2023-03-29 11:57:27 -07:00
Neil Bickford
344669ddf6 Update stb_image and stb_image_write to latest nothings/stb dev branch: 9f1776a36d 2023-03-29 11:56:13 -07:00
Syoyo Fujita
967c98dd90 Merge pull request #408 from marco-langer/feature/ostream_error_handling
Added error checking to ostream writing
2023-03-13 21:18:02 +09:00
Marco Langer
7658624bb4 Added error handling to ostream writing 2023-03-12 19:26:05 +01:00
26 changed files with 11769 additions and 1604 deletions

View File

@@ -0,0 +1,92 @@
# Copilot Review Instructions for TinyGLTF
This document provides guidelines for reviewing code changes in the TinyGLTF repository.
## Memory Safety
- **Buffer Overflows**: Check for proper bounds checking when accessing arrays, vectors, and buffers. Verify that buffer sizes are validated before read/write operations.
- **Null Pointer Dereferences**: Ensure all pointers are checked for null before dereferencing, especially when handling optional glTF fields.
- **Memory Leaks**: Verify proper resource management, including RAII patterns for file handles, image data, and dynamically allocated memory.
- **Use-After-Free**: Check for proper lifetime management of objects, especially when dealing with callbacks and asynchronous operations.
## Error Handling
- **File I/O**: Verify that all file operations have proper error checking and meaningful error messages.
- **JSON Parsing**: Ensure JSON parsing errors are caught and reported with helpful context about the location and nature of the error.
- **Resource Loading**: Check that failures in loading images, buffers, and other resources are properly handled and don't cause crashes.
- **Error Propagation**: Verify that errors are properly propagated through the call stack with appropriate error messages.
## glTF 2.0 Specification Compliance
- **Required Fields**: Ensure all required glTF fields are validated and present.
- **Data Types**: Verify that data types match the glTF specification (e.g., component types, accessor types).
- **Constraints**: Check that glTF constraints are enforced (e.g., valid ranges for enums, buffer stride requirements).
- **Extensions**: Verify proper handling of glTF extensions and that unknown extensions are handled gracefully.
- **Validation**: Ensure new features align with the glTF 2.0 specification from the Khronos Group.
## Cross-Platform Compatibility
- **Windows**: Check for proper handling of Windows-specific issues (path separators, line endings, file operations).
- **Linux**: Verify compatibility with various Linux distributions and compilers (GCC, Clang).
- **macOS**: Ensure macOS-specific considerations are addressed (case-sensitive filesystems, Clang compatibility).
- **Mobile Platforms**: Consider Android and iOS compatibility where applicable.
- **Endianness**: Verify proper handling of byte order when reading binary data.
- **Compiler Compatibility**: Ensure code compiles with C++11 standard and supported compilers (MSVC, GCC, Clang).
## Edge Cases in glTF Parsing
- **Empty/Minimal Files**: Verify handling of minimal valid glTF files.
- **Large Files**: Check for proper handling of large glTF files and buffers without memory exhaustion.
- **Malformed Data**: Ensure graceful handling of malformed or invalid glTF data.
- **Missing Optional Fields**: Verify correct behavior when optional glTF fields are absent.
- **Edge Values**: Check handling of boundary values (e.g., maximum buffer sizes, extreme floating-point values).
- **Base64 Encoding**: Verify proper handling of base64-encoded data URIs and invalid encodings.
## Backwards Compatibility
- **API Changes**: Ensure public API changes maintain backwards compatibility or are properly deprecated.
- **Breaking Changes**: Flag any breaking changes for major version updates and document migration paths.
- **Binary Compatibility**: Consider ABI stability for header-only library changes.
- **Default Behavior**: Verify that default behavior of existing functionality remains unchanged.
## Performance Considerations
- **Parsing Performance**: Check for unnecessary copies, redundant allocations, and inefficient algorithms in parsing logic.
- **Memory Usage**: Verify efficient memory usage, especially when loading large glTF files.
- **I/O Operations**: Ensure efficient file reading and minimize unnecessary disk access.
- **String Operations**: Check for efficient string handling (use of string_view, move semantics).
- **STL Usage**: Verify appropriate use of STL containers and algorithms.
## Documentation
- **Public API**: Ensure all public functions, classes, and methods have clear documentation comments.
- **Parameters**: Verify that function parameters are documented, including expected ranges and constraints.
- **Return Values**: Document return values and possible error conditions.
- **Examples**: Check that complex features include usage examples.
- **Changelog**: Verify that significant changes are documented in release notes or changelog.
## Testing
- **Test Coverage**: Ensure new features include appropriate unit tests or integration tests.
- **Edge Cases**: Verify that tests cover edge cases and error conditions.
- **Cross-Platform Tests**: Check that tests run on all supported platforms.
- **Regression Tests**: Ensure bug fixes include regression tests to prevent recurrence.
- **Sample Files**: Verify that changes are tested with various valid and invalid glTF sample files.
## Code Style Consistency
- **Header-Only Pattern**: Maintain the header-only library structure.
- **Naming Conventions**: Follow existing naming conventions (CamelCase for types, snake_case for functions where applicable).
- **Formatting**: Adhere to the existing code formatting style (check `.clang-format` if available).
- **Include Guards**: Verify proper include guards and header organization.
- **Namespace Usage**: Ensure proper use of the `tinygltf` namespace.
- **Comments**: Maintain consistent comment style with existing code.
- **C++11 Compliance**: Verify that code uses C++11 features appropriately and doesn't require newer standards unless specified.
## Additional Considerations
- **Third-Party Dependencies**: Minimize new dependencies; prefer existing dependencies (json.hpp, stb_image).
- **Warnings**: Ensure code compiles without warnings on supported compilers.
- **const Correctness**: Verify proper use of const for parameters and methods.
- **RAII**: Prefer RAII patterns for resource management over manual cleanup.
- **noexcept**: Use noexcept appropriately for move constructors and move assignment operators.

View File

@@ -4,41 +4,43 @@ on: [push, pull_request]
jobs:
# compile with older gcc4.8
build-gcc48:
# gcc4.8 is too old and ubuntu-18.04 image is not supported in GitHub Actions anymore,
# so disable this build.
## compile with older gcc4.8
#build-gcc48:
runs-on: ubuntu-18.04
name: Build with gcc 4.8
# runs-on: ubuntu-18.04
# name: Build with gcc 4.8
steps:
- name: Checkout
uses: actions/checkout@v1
# steps:
# - name: Checkout
# uses: actions/checkout@v1
- name: Build
run: |
sudo apt-get update
sudo apt-get install -y build-essential
sudo apt-get install -y gcc-4.8 g++-4.8
g++-4.8 -std=c++11 -o loader_example loader_example.cc
# - name: Build
# run: |
# sudo apt-get update
# sudo apt-get install -y build-essential
# sudo apt-get install -y gcc-4.8 g++-4.8
# g++-4.8 -std=c++11 -o loader_example loader_example.cc
- name: NoexceptBuild
run: |
g++-4.8 -DTINYGLTF_NOEXCEPTION -std=c++11 -o loader_example loader_example.cc
# - name: NoexceptBuild
# run: |
# g++-4.8 -DTINYGLTF_NOEXCEPTION -std=c++11 -o loader_example loader_example.cc
- name: RapidjsonBuild
run: |
git clone https://github.com/Tencent/rapidjson
g++-4.8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -o loader_example loader_example.cc
# - name: RapidjsonBuild
# run: |
# git clone https://github.com/Tencent/rapidjson
# g++-4.8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -o loader_example loader_example.cc
# compile with mingw gcc cross
build-mingw-cross:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
name: Build with MinGW gcc cross
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Build
run: |
@@ -133,20 +135,20 @@ jobs:
# Cross-compile for aarch64 linux target
build-cross-aarch64:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
name: Build on cross aarch64
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- name: Build
run: |
sudo apt-get update
sudo apt-get install -y build-essential
sudo apt-get install -y gcc-8-aarch64-linux-gnu g++-8-aarch64-linux-gnu
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
git clone https://github.com/Tencent/rapidjson
aarch64-linux-gnu-g++-8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -g -O0 -o loader_example loader_example.cc
aarch64-linux-gnu-g++ -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -g -O0 -o loader_example loader_example.cc
# macOS clang
build-macos:

330
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,330 @@
name: Comprehensive CI
on:
push:
branches:
- master
- release
- devel
pull_request:
branches:
- master
- release
- devel
workflow_dispatch:
permissions:
contents: read
jobs:
# Linux x64 - GCC
linux-gcc-x64:
runs-on: ubuntu-latest
name: Linux x64 (GCC)
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: cmake -B build -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON
- name: Build
run: cmake --build build
- name: Run loader_example
run: ./build/loader_example models/Cube/Cube.gltf
- name: Run tests
run: ctest --test-dir build --output-on-failure
# Linux x64 - Clang 21
linux-clang-x64:
runs-on: ubuntu-24.04
name: Linux x64 (Clang 21)
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Clang 21
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21
- name: Configure
run: |
cmake -B build -DCMAKE_C_COMPILER=clang-21 -DCMAKE_CXX_COMPILER=clang++-21 -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON
- name: Build
run: cmake --build build
- name: Run loader_example
run: |
./build/loader_example models/Cube/Cube.gltf
- name: Run tests
run: ctest --test-dir build --output-on-failure
# Linux ARM64 - GCC (native)
linux-arm64:
runs-on: ubuntu-24.04-arm
name: Linux ARM64 (GCC)
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: cmake -B build -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON
- name: Build
run: cmake --build build
- name: Run loader_example
run: ./build/loader_example models/Cube/Cube.gltf
- name: Run tests
run: ctest --test-dir build --output-on-failure
# macOS ARM64 Apple Silicon
macos-arm64:
runs-on: macos-14
name: macOS ARM64 Apple Silicon (Clang)
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: cmake -B build -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON
- name: Build
run: cmake --build build
- name: Run loader_example
run: ./build/loader_example models/Cube/Cube.gltf
- name: Run tests
run: ctest --test-dir build --output-on-failure
# Windows x64 - MSVC
windows-msvc-x64:
runs-on: windows-latest
name: Windows x64 (MSVC)
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: |
mkdir build
cd build
cmake -G "Visual Studio 17 2022" -A x64 -DTINYGLTF_BUILD_LOADER_EXAMPLE=On -DTINYGLTF_BUILD_GL_EXAMPLES=Off -DTINYGLTF_BUILD_VALIDATOR_EXAMPLE=Off ..
- name: Build
run: cmake --build build --config Release
- name: Run loader_example
run: |
.\build\Release\loader_example.exe models\Cube\Cube.gltf
- name: Run tests
run: ctest --test-dir build -C Release --output-on-failure
# Windows x86 - MSVC
windows-msvc-x86:
runs-on: windows-latest
name: Windows x86 (MSVC)
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: |
mkdir build
cd build
cmake -G "Visual Studio 17 2022" -A Win32 -DTINYGLTF_BUILD_LOADER_EXAMPLE=On -DTINYGLTF_BUILD_GL_EXAMPLES=Off -DTINYGLTF_BUILD_VALIDATOR_EXAMPLE=Off ..
- name: Build
run: cmake --build build --config Release
- name: Run tests
run: ctest --test-dir build -C Release --output-on-failure
# Windows ARM64 - MSVC (cross-compile)
windows-msvc-arm64:
runs-on: windows-latest
name: Windows ARM64 (MSVC) - Cross-compile
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure
run: |
mkdir build
cd build
cmake -G "Visual Studio 17 2022" -A ARM64 -DTINYGLTF_BUILD_LOADER_EXAMPLE=On -DTINYGLTF_BUILD_GL_EXAMPLES=Off -DTINYGLTF_BUILD_VALIDATOR_EXAMPLE=Off ..
- name: Build
run: cmake --build build --config Release
# Windows MinGW - MSYS2
windows-mingw-msys2:
runs-on: windows-latest
name: Windows x64 (MinGW MSYS2)
defaults:
run:
shell: msys2 {0}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
install: base-devel
pacboy: >-
cc:p cmake:p ninja:p
update: true
release: false
- name: Build with CMake
run: |
cmake -G"Ninja" -S . -B build
cmake --build build
- name: Run loader_example
run: |
./build/loader_example models/Cube/Cube.gltf
- name: Run tests
run: ctest --test-dir build --output-on-failure
# Linux -> Windows MinGW Cross-compile
linux-mingw-cross:
runs-on: ubuntu-latest
name: Linux→Windows (MinGW Cross) - Build Only
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install MinGW
run: |
sudo apt-get update
sudo apt-get install -y build-essential mingw-w64
- name: Build
run: |
x86_64-w64-mingw32-g++ -std=c++11 -o loader_example.exe loader_example.cc
# Special Configuration: No Exceptions
linux-noexception:
runs-on: ubuntu-latest
name: Linux x64 (GCC) - No Exceptions
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build loader_example
run: |
g++ -DTINYGLTF_NOEXCEPTION -std=c++11 -o loader_example loader_example.cc
- name: Run loader_example
run: |
./loader_example models/Cube/Cube.gltf
- name: Build and run unit tests
run: |
cd tests
g++ -DTINYGLTF_NOEXCEPTION -I../ -std=c++11 -g -O0 -o tester_noexcept tester.cc
./tester_noexcept
# Special Configuration: Header-Only Mode
linux-header-only:
runs-on: ubuntu-latest
name: Linux x64 (GCC) - Header-Only Mode
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build with CMake Header-Only
run: |
mkdir build
cmake -B build -DTINYGLTF_HEADER_ONLY=ON -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON
cmake --build build
- name: Run loader_example
run: |
./build/loader_example models/Cube/Cube.gltf
- name: Run tests
run: ctest --test-dir build --output-on-failure
# Special Configuration: RapidJSON Backend
linux-rapidjson:
runs-on: ubuntu-latest
name: Linux x64 (GCC) - RapidJSON Backend
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Clone RapidJSON
run: |
git clone https://github.com/Tencent/rapidjson
- name: Configure
run: |
cmake -B build -DTINYGLTF_USE_RAPIDJSON=ON -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON -DCMAKE_PREFIX_PATH=$PWD/rapidjson
- name: Build
run: cmake --build build
- name: Run loader_example
run: |
./build/loader_example models/Cube/Cube.gltf
- name: Run tests
run: ctest --test-dir build --output-on-failure
# Special Configuration: AddressSanitizer
linux-asan:
runs-on: ubuntu-latest
name: Linux x64 (Clang) - AddressSanitizer
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build loader_example with ASan
run: |
clang++ -fsanitize=address -std=c++11 -g -O1 -o loader_example loader_example.cc
- name: Run loader_example
run: |
./loader_example models/Cube/Cube.gltf
- name: Build and run unit tests with ASan
run: |
cd tests
clang++ -fsanitize=address -I../ -std=c++11 -g -O1 -o tester tester.cc
./tester
# Special Configuration: UndefinedBehaviorSanitizer
linux-ubsan:
runs-on: ubuntu-latest
name: Linux x64 (Clang) - UndefinedBehaviorSanitizer
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build loader_example with UBSan
run: |
clang++ -fsanitize=undefined -std=c++11 -g -O1 -o loader_example loader_example.cc
- name: Run loader_example
run: |
./loader_example models/Cube/Cube.gltf
- name: Build and run unit tests with UBSan
run: |
cd tests
clang++ -fsanitize=undefined -I../ -std=c++11 -g -O1 -o tester tester.cc
./tester

45
.github/workflows/mingw-w64-msys2.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
name: MSYS2 MinGW-w64 Windows 64bit Build
on:
push:
branches:
- release
- devel
paths:
- 'tiny_gltf.*'
- 'CMakeLists.txt'
- '.github/workflows/mingw-w64-msys2.yml'
pull_request:
workflow_dispatch:
jobs:
mingw-w64-msys2-build:
name: MSYS2 MinGW-w64 Windows Build
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v3
- name: Install core & build dependencies
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
install: base-devel
pacboy: >-
cc:p cmake:p ninja:p
update: true
release: false
- name: Configure
run: |
cmake \
-G"Ninja" \
-S . \
-B build
- name: Build
run: |
cmake --build build

17
.gitignore vendored
View File

@@ -21,9 +21,13 @@ premake5.tar.gz
*.vcxproj*
.vs
# default cmake build dir
build/
#binary directories
bin/
obj/
out/
#runtime gui config
imgui.ini
@@ -69,6 +73,19 @@ tests/tester
tests/tester_noexcept
tests/issue-97.gltf
tests/issue-261.gltf
tests/issue-495-external.gltf
# Test-generated output files (written by tester.cc during test run)
tests/Cube.gltf
tests/Cube.bin
tests/Cube.glb
tests/Cube_BaseColor.png
tests/Cube_MetallicRoughness.png
tests/Cube_with_embedded_images.gltf
tests/Cube_with_image_files.gltf
tests/tmp.glb
tests/ issue-236.gltf
tests/ issue-236.bin
tests/ 2x2 image has multiple spaces.png
# unignore
!Makefile

View File

@@ -1,10 +0,0 @@
#!/bin/bash
if [[ "$TRAVIS_OS_NAME" == "osx" ]]
then
brew upgrade
curl -o premake5.tar.gz https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-macosx.tar.gz
else
wget https://github.com/premake/premake-core/releases/download/v5.0.0-alpha12/premake-5.0.0-alpha12-linux.tar.gz -O premake5.tar.gz
fi
tar xzf premake5.tar.gz

View File

@@ -1,63 +0,0 @@
language: cpp
sudo: false
matrix:
include:
- addons: &1
apt:
sources:
- george-edison55-precise-backports
- ubuntu-toolchain-r-test
- llvm-toolchain-trusty-3.9
packages:
- g++-4.9
- clang-3.9
compiler: clang
env: COMPILER_VERSION=3.9 BUILD_TYPE=Debug
- addons: *1
compiler: clang
env: COMPILER_VERSION=3.9 BUILD_TYPE=Release
- addons: &2
apt:
sources:
- george-edison55-precise-backports
- ubuntu-toolchain-r-test
packages:
- g++-4.9
compiler: gcc
env: COMPILER_VERSION=4.9 BUILD_TYPE=Debug EXTRA_CXXFLAGS="-fsanitize=address"
- addons: *2
compiler: gcc
env: COMPILER_VERSION=4.9 BUILD_TYPE=Release EXTRA_CXXFLAGS="-fsanitize=address"
- addons: *1
compiler: clang
env: COMPILER_VERSION=3.9 BUILD_TYPE=Debug CFLAGS="-O0" CXXFLAGS="-O0"
- addons: &3
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
compiler: gcc
env: COMPILER_VERSION=4.8 BUILD_TYPE=Debug
- addons: *3
compiler: gcc
env: COMPILER_VERSION=4.8 BUILD_TYPE=Release
before_install:
- ./.travis-before-install.sh
script:
- export CC="${CC}-${COMPILER_VERSION}"
- export CXX="${CXX}-${COMPILER_VERSION}"
- ${CC} -v
- ${CXX} ${EXTRA_CXXFLAGS} -std=c++11 -Wall -g -o loader_example loader_example.cc
- ./loader_example ./models/Cube/Cube.gltf
- cd tests
- clang++ -v
- make
- ./tester
- ./tester_noexcept
- cd ../examples/raytrace
- ../../premake5 gmake
- make

View File

@@ -1,38 +1,63 @@
cmake_minimum_required(VERSION 3.6)
PROJECT (tinygltf)
project(tinygltf)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
SET(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED On)
set(CMAKE_CXX_EXTENSIONS Off)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(TINYGLTF_BUILD_LOADER_EXAMPLE "Build loader_example(load glTF and dump infos)" ON)
option(TINYGLTF_BUILD_GL_EXAMPLES "Build GL exampels(requires glfw, OpenGL, etc)" OFF)
option(TINYGLTF_BUILD_VALIDATOR_EXAMPLE "Build validator exampe" OFF)
option(TINYGLTF_BUILD_BUILDER_EXAMPLE "Build glTF builder example" OFF)
option(TINYGLTF_BUILD_TESTS "Build unit tests" OFF)
option(TINYGLTF_HEADER_ONLY "On: header-only mode. Off: create tinygltf library(No TINYGLTF_IMPLEMENTATION required in your project)" OFF)
option(TINYGLTF_INSTALL "Install tinygltf files during install step. Usually set to OFF if you include tinygltf through add_subdirectory()" ON)
option(TINYGLTF_INSTALL_VENDOR "Install vendored nlohmann/json and nothings/stb headers" ON)
option(TINYGLTF_USE_CUSTOM_JSON "Use the built-in fast JSON parser (tinygltf_json.h) instead of nlohmann/json" OFF)
if (TINYGLTF_BUILD_LOADER_EXAMPLE)
ADD_EXECUTABLE ( loader_example
add_executable(loader_example
loader_example.cc
)
endif (TINYGLTF_BUILD_LOADER_EXAMPLE)
if (TINYGLTF_BUILD_GL_EXAMPLES)
ADD_SUBDIRECTORY ( examples/gltfutil )
ADD_SUBDIRECTORY ( examples/glview )
add_subdirectory( examples/gltfutil )
add_subdirectory( examples/glview )
endif (TINYGLTF_BUILD_GL_EXAMPLES)
if (TINYGLTF_BUILD_VALIDATOR_EXAMPLE)
ADD_SUBDIRECTORY ( examples/validator )
add_subdirectory( examples/validator )
endif (TINYGLTF_BUILD_VALIDATOR_EXAMPLE)
if (TINYGLTF_BUILD_BUILDER_EXAMPLE)
ADD_SUBDIRECTORY ( examples/build-gltf )
add_subdirectory ( examples/build-gltf )
endif (TINYGLTF_BUILD_BUILDER_EXAMPLE)
if (TINYGLTF_BUILD_TESTS)
enable_testing()
add_executable(tester tests/tester.cc)
target_include_directories(tester PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
)
add_test(NAME tester COMMAND tester WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
# Build and run tests with the custom JSON backend enabled to catch regressions
add_executable(tester_customjson tests/tester.cc)
target_include_directories(tester_customjson PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
)
target_compile_definitions(tester_customjson PRIVATE TINYGLTF_USE_CUSTOM_JSON)
add_test(NAME tester_customjson COMMAND tester_customjson WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
endif (TINYGLTF_BUILD_TESTS)
#
# for add_subdirectory and standalone build
#
@@ -56,21 +81,37 @@ else (TINYGLTF_HEADER_ONLY)
)
endif (TINYGLTF_HEADER_ONLY)
if (TINYGLTF_USE_CUSTOM_JSON)
if (TINYGLTF_HEADER_ONLY)
target_compile_definitions(tinygltf INTERFACE TINYGLTF_USE_CUSTOM_JSON)
else ()
target_compile_definitions(tinygltf PUBLIC TINYGLTF_USE_CUSTOM_JSON)
endif ()
endif ()
if (TINYGLTF_INSTALL)
install(TARGETS tinygltf EXPORT tinygltfTargets)
install(EXPORT tinygltfTargets NAMESPACE tinygltf:: FILE TinyGLTFTargets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake)
install(EXPORT tinygltfTargets NAMESPACE tinygltf:: FILE TinyGLTFTargets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/tinygltf)
configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/TinyGLTFConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/TinyGLTFConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TinyGLTFConfig.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TinyGLTFConfig.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/tinygltf)
# Do not install .lib even if !TINYGLTF_HEADER_ONLY
INSTALL ( FILES
json.hpp
stb_image.h
stb_image_write.h
tiny_gltf.h
tinygltf_json.h
${TINYGLTF_EXTRA_SOUECES}
DESTINATION
include
)
if(TINYGLTF_INSTALL_VENDOR)
INSTALL ( FILES
json.hpp
stb_image.h
stb_image_write.h
DESTINATION
include
)
endif()
endif(TINYGLTF_INSTALL)

View File

@@ -9,7 +9,7 @@ If you are looking for old, C++03 version, please use `devel-picojson` branch (b
## Status
Currently TinyGLTF is stable and maintenance mode. No drastic changes and feature additions planned.
- v2.9.0 Various fixes and improvements. Filesystem callback API change.
- v2.8.0 Add URICallbacks for custom URI handling in Buffer and Image. PR#397
- v2.7.0 Change WriteImageDataFunction user callback function signature. PR#393
- v2.6.0 Support serializing sparse accessor(Thanks to @fynv).
@@ -26,10 +26,6 @@ Currently TinyGLTF is stable and maintenance mode. No drastic changes and featur
## Builds
[![Build Status](https://travis-ci.org/syoyo/tinygltf.svg?branch=devel)](https://travis-ci.org/syoyo/tinygltf)
[![Build status](https://ci.appveyor.com/api/projects/status/warngenu9wjjhlm8?svg=true)](https://ci.appveyor.com/project/syoyo/tinygltf)
![C/C++ CI](https://github.com/syoyo/tinygltf/workflows/C/C++%20CI/badge.svg)
## Features
@@ -109,6 +105,7 @@ WASI build example is located in [wasm](wasm) .
* [SanityEngine](https://github.com/DethRaid/SanityEngine) - A C++/D3D12 renderer focused on the personal and professional development of its developer
* [Open3D](http://www.open3d.org/) - A Modern Library for 3D Data Processing
* [Supernova Engine](https://github.com/supernovaengine/supernova) - Game engine for 2D and 3D projects with Lua or C++ in data oriented design.
* [Wicked Engine<img src="https://github.com/turanszkij/WickedEngine/blob/master/Content/logo_small.png" width="28px" align="center"/>](https://github.com/turanszkij/WickedEngine) - 3D engine with modern graphics
* Your projects here! (Please send PR)
## TODOs
@@ -160,9 +157,10 @@ Model model;
TinyGLTF loader;
std::string err;
std::string warn;
std::string filename = "input.gltf";
bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, argv[1]);
//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, argv[1]); // for binary glTF(.glb)
bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, filename);
//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, filename); // for binary glTF(.glb)
if (!warn.empty()) {
printf("Warn: %s\n", warn.c_str());
@@ -173,8 +171,7 @@ if (!err.empty()) {
}
if (!ret) {
printf("Failed to parse glTF\n");
return -1;
printf("Failed to parse glTF: %s\n", filename.c_str());
}
```
@@ -195,7 +192,7 @@ if (!ret) {
* `TINYGLTF_NO_INCLUDE_STB_IMAGE `: Disable including `stb_image.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
* `TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE `: Disable including `stb_image_write.h` from within `tiny_gltf.h` because it has been already included before or you want to include it using custom path before including `tiny_gltf.h`.
* `TINYGLTF_USE_RAPIDJSON` : Use RapidJSON as a JSON parser/serializer. RapidJSON files are not included in TinyGLTF repo. Please set an include path to RapidJSON if you enable this feature.
* `TINYGLTF_USE_CPP14` : Use C++14 feature(requires C++14 compiler). This may give better performance than C++11.
## CMake options
@@ -211,6 +208,11 @@ set(TINYGLTF_INSTALL OFF CACHE INTERNAL "" FORCE)
add_subdirectory(/path/to/tinygltf)
```
NOTE: Using tinygltf as a submodule doesn't automatically add the headers to your include path (as standard for many libraries). To get this functionality, add the following to the CMakeLists.txt file from above:
```
target_include_directories(${PROJECT_NAME} PRIVATE "/path/to/tinygltf")
```
### Saving gltTF 2.0 model

View File

@@ -1,18 +0,0 @@
version: 0.9.{build}
image:
- Visual Studio 2015
# scripts that runs after repo cloning.
install:
- vcsetup.bat
platform: x64
configuration: Release
build:
parallel: true
project: TinyGLTFSolution.sln
after_build:
- examples.bat

70
benchmark/Makefile Normal file
View File

@@ -0,0 +1,70 @@
# benchmark/Makefile — Build and run tinygltf v3 benchmarks
#
# Targets:
# make — build gen_synthetic + bench_v3
# make generate — generate synthetic test scenes
# make run — run benchmarks on all generated scenes
# make report — run benchmarks and produce CSV report
# make clean — remove binaries and generated scenes
CXX ?= g++
CXXFLAGS ?= -O2 -std=c++17 -Wall -Wextra -Wno-unused-function
CXXFLAGS += -fno-rtti -fno-exceptions
INCLUDES = -I..
BINDIR = .
GEN = $(BINDIR)/gen_synthetic
BENCH_V3 = $(BINDIR)/bench_v3
# Iteration counts
ITERATIONS ?= 10
WARMUP ?= 2
PREFIX ?= synthetic
.PHONY: all generate run report clean
all: $(GEN) $(BENCH_V3)
$(GEN): gen_synthetic.cpp
$(CXX) $(CXXFLAGS) -o $@ $<
$(BENCH_V3): bench_v3.cpp ../tiny_gltf_v3.h ../tinygltf_json.h
$(CXX) $(CXXFLAGS) $(INCLUDES) -o $@ $<
# Generate synthetic scenes of varying sizes
generate: $(GEN)
@echo "=== Generating synthetic scenes ==="
./$(GEN) --prefix $(PREFIX)
@echo ""
@echo "Generated files (binary + GLB):"
@ls -lh $(PREFIX)_*.gltf $(PREFIX)_*.glb $(PREFIX)_*.bin 2>/dev/null || true
# Run benchmarks on all generated scenes
run: $(BENCH_V3) generate
@echo ""
@echo "================================================================="
@echo " tinygltf v3 Benchmark"
@echo "================================================================="
@echo ""
@for f in $(PREFIX)_*.glb $(PREFIX)_*.gltf; do \
if [ -f "$$f" ]; then \
./$(BENCH_V3) "$$f" --iterations $(ITERATIONS) --warmup $(WARMUP); \
echo ""; \
fi; \
done
# Run benchmarks and produce CSV report
report: $(BENCH_V3) generate
@echo "file,size_bytes,iterations,parse_min_ms,parse_max_ms,parse_avg_ms,parse_median_ms,throughput_mbs,arena_peak_bytes,meshes,nodes,accessors,materials,animations" > benchmark_report.csv
@for f in $(PREFIX)_*.glb $(PREFIX)_*.gltf; do \
if [ -f "$$f" ]; then \
./$(BENCH_V3) "$$f" --iterations $(ITERATIONS) --warmup $(WARMUP) --csv | tail -1 >> benchmark_report.csv; \
fi; \
done
@echo "=== Report written to benchmark_report.csv ==="
@cat benchmark_report.csv | column -t -s,
clean:
rm -f $(GEN) $(BENCH_V3)
rm -f $(PREFIX)_*.gltf $(PREFIX)_*.glb $(PREFIX)_*.bin
rm -f benchmark_report.csv

396
benchmark/bench_v3.cpp Normal file
View File

@@ -0,0 +1,396 @@
/*
* bench_v3.cpp — Benchmark tinygltf v3 parser: parse speed & memory.
*
* Measures:
* - File read time
* - JSON parse + model build time
* - Peak arena memory usage
* - Throughput (MB/s)
*
* Usage:
* bench_v3 <file.gltf|file.glb> [--iterations N] [--warmup N] [--quiet]
* bench_v3 --batch <file1> <file2> ... [--iterations N]
*/
#define TINYGLTF3_IMPLEMENTATION
#define TINYGLTF3_ENABLE_FS
#include "tiny_gltf_v3.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <string>
#include <algorithm>
#include <chrono>
#if defined(__linux__)
#include <sys/resource.h>
#endif
/* ------------------------------------------------------------------ */
/* Timing helpers */
/* ------------------------------------------------------------------ */
using Clock = std::chrono::high_resolution_clock;
using TimePoint = Clock::time_point;
static double elapsed_ms(TimePoint start, TimePoint end) {
return std::chrono::duration<double, std::milli>(end - start).count();
}
/* ------------------------------------------------------------------ */
/* Memory tracking allocator */
/* ------------------------------------------------------------------ */
struct MemTracker {
size_t current;
size_t peak;
size_t total_allocs;
size_t total_frees;
};
static void *tracked_alloc(size_t size, void *ud) {
MemTracker *mt = (MemTracker *)ud;
void *ptr = malloc(size);
if (ptr) {
mt->current += size;
if (mt->current > mt->peak) mt->peak = mt->current;
mt->total_allocs++;
}
return ptr;
}
static void *tracked_realloc(void *ptr, size_t old_size, size_t new_size, void *ud) {
MemTracker *mt = (MemTracker *)ud;
void *new_ptr = realloc(ptr, new_size);
if (new_ptr) {
mt->current -= old_size;
mt->current += new_size;
if (mt->current > mt->peak) mt->peak = mt->current;
}
return new_ptr;
}
static void tracked_free(void *ptr, size_t size, void *ud) {
MemTracker *mt = (MemTracker *)ud;
if (ptr) {
mt->current -= size;
mt->total_frees++;
free(ptr);
}
}
/* ------------------------------------------------------------------ */
/* RSS measurement (Linux) */
/* ------------------------------------------------------------------ */
static size_t get_rss_bytes() {
#if defined(__linux__)
FILE *f = fopen("/proc/self/statm", "r");
if (!f) return 0;
long pages = 0;
if (fscanf(f, "%*s %ld", &pages) != 1) pages = 0;
fclose(f);
return (size_t)pages * 4096;
#else
return 0;
#endif
}
/* ------------------------------------------------------------------ */
/* Benchmark result */
/* ------------------------------------------------------------------ */
struct BenchResult {
std::string filename;
uint64_t file_size;
int iterations;
/* Parse timing (ms) */
double parse_min;
double parse_max;
double parse_avg;
double parse_median;
/* Memory */
size_t arena_peak; /* Peak arena allocation */
size_t rss_before;
size_t rss_after;
/* Model stats */
uint32_t meshes;
uint32_t nodes;
uint32_t accessors;
uint32_t materials;
uint32_t animations;
uint32_t buffers;
uint32_t buffer_views;
uint32_t images;
uint32_t textures;
/* Derived */
double throughput_mbs; /* MB/s based on median */
};
/* ------------------------------------------------------------------ */
/* Run benchmark for a single file */
/* ------------------------------------------------------------------ */
static BenchResult bench_file(const char *filename, int iterations, int warmup,
bool quiet, int float32_mode = 0) {
BenchResult r = {};
r.filename = filename;
r.iterations = iterations;
/* Read file into memory */
FILE *f = fopen(filename, "rb");
if (!f) {
fprintf(stderr, "ERROR: Cannot open '%s'\n", filename);
return r;
}
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
if (sz <= 0) { fclose(f); return r; }
std::vector<uint8_t> data((size_t)sz);
size_t rd = fread(data.data(), 1, (size_t)sz, f);
fclose(f);
if ((long)rd != sz) { return r; }
r.file_size = (uint64_t)sz;
/* Extract base dir */
std::string path(filename);
std::string base_dir;
size_t sep = path.find_last_of("/\\");
if (sep != std::string::npos) base_dir = path.substr(0, sep);
/* Warmup iterations (not measured) */
for (int i = 0; i < warmup; ++i) {
tg3_model model;
tg3_error_stack errors;
tg3_error_stack_init(&errors);
tg3_parse_auto(&model, &errors, data.data(), data.size(),
base_dir.c_str(), (uint32_t)base_dir.size(), NULL);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
}
/* Benchmark iterations */
std::vector<double> times;
times.reserve(iterations);
MemTracker tracker_best;
memset(&tracker_best, 0, sizeof(tracker_best));
r.rss_before = get_rss_bytes();
for (int i = 0; i < iterations; ++i) {
MemTracker tracker;
memset(&tracker, 0, sizeof(tracker));
tg3_parse_options opts;
tg3_parse_options_init(&opts);
opts.memory.allocator.alloc = tracked_alloc;
opts.memory.allocator.realloc = tracked_realloc;
opts.memory.allocator.free = tracked_free;
opts.memory.allocator.user_data = &tracker;
opts.parse_float32 = float32_mode;
tg3_model model;
tg3_error_stack errors;
tg3_error_stack_init(&errors);
TimePoint t0 = Clock::now();
tg3_error_code err = tg3_parse_auto(&model, &errors,
data.data(), data.size(),
base_dir.c_str(),
(uint32_t)base_dir.size(),
&opts);
TimePoint t1 = Clock::now();
double ms = elapsed_ms(t0, t1);
times.push_back(ms);
/* Capture model stats on first successful iteration */
if (i == 0 && err == TG3_OK) {
r.meshes = model.meshes_count;
r.nodes = model.nodes_count;
r.accessors = model.accessors_count;
r.materials = model.materials_count;
r.animations = model.animations_count;
r.buffers = model.buffers_count;
r.buffer_views = model.buffer_views_count;
r.images = model.images_count;
r.textures = model.textures_count;
}
if (tracker.peak > tracker_best.peak) {
tracker_best = tracker;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
if (err != TG3_OK && !quiet) {
fprintf(stderr, " Parse error on iteration %d: code %d\n", i, (int)err);
}
}
r.rss_after = get_rss_bytes();
r.arena_peak = tracker_best.peak;
/* Compute stats */
std::sort(times.begin(), times.end());
r.parse_min = times.front();
r.parse_max = times.back();
double sum = 0;
for (double t : times) sum += t;
r.parse_avg = sum / times.size();
r.parse_median = times[times.size() / 2];
/* Throughput: file_size / median_time */
if (r.parse_median > 0) {
r.throughput_mbs = ((double)r.file_size / (1024.0 * 1024.0)) /
(r.parse_median / 1000.0);
}
return r;
}
/* ------------------------------------------------------------------ */
/* Print results */
/* ------------------------------------------------------------------ */
static const char *human_bytes(size_t bytes, char *buf, size_t buf_sz) {
if (bytes >= 1024ULL * 1024 * 1024)
snprintf(buf, buf_sz, "%.2f GB", (double)bytes / (1024.0 * 1024 * 1024));
else if (bytes >= 1024 * 1024)
snprintf(buf, buf_sz, "%.2f MB", (double)bytes / (1024.0 * 1024));
else if (bytes >= 1024)
snprintf(buf, buf_sz, "%.2f KB", (double)bytes / 1024.0);
else
snprintf(buf, buf_sz, "%zu B", bytes);
return buf;
}
static void print_result(const BenchResult &r) {
char buf1[64], buf2[64];
printf("┌─────────────────────────────────────────────────────────────────┐\n");
printf("│ %-63s │\n", r.filename.c_str());
printf("├─────────────────────────────────────────────────────────────────┤\n");
printf("│ File size: %-47s │\n", human_bytes((size_t)r.file_size, buf1, sizeof(buf1)));
printf("│ Iterations: %-47d │\n", r.iterations);
printf("│ │\n");
printf("│ Parse time (ms): │\n");
printf("│ min: %10.3f │\n", r.parse_min);
printf("│ max: %10.3f │\n", r.parse_max);
printf("│ avg: %10.3f │\n", r.parse_avg);
printf("│ median: %10.3f │\n", r.parse_median);
printf("│ │\n");
printf("│ Throughput: %-47s │\n",
(snprintf(buf1, sizeof(buf1), "%.2f MB/s", r.throughput_mbs), buf1));
printf("│ Arena peak: %-47s │\n", human_bytes(r.arena_peak, buf1, sizeof(buf1)));
if (r.rss_before > 0) {
printf("│ RSS before: %-47s │\n", human_bytes(r.rss_before, buf1, sizeof(buf1)));
printf("│ RSS after: %-47s │\n", human_bytes(r.rss_after, buf2, sizeof(buf2)));
}
printf("│ │\n");
printf("│ Model: %u meshes, %u nodes, %u accessors, %u materials",
r.meshes, r.nodes, r.accessors, r.materials);
printf("\n");
printf("│ %u animations, %u buffers, %u images",
r.animations, r.buffers, r.images);
printf("\n");
printf("└─────────────────────────────────────────────────────────────────┘\n");
}
static void print_csv_header() {
printf("file,size_bytes,iterations,parse_min_ms,parse_max_ms,parse_avg_ms,"
"parse_median_ms,throughput_mbs,arena_peak_bytes,"
"meshes,nodes,accessors,materials,animations\n");
}
static void print_csv_row(const BenchResult &r) {
printf("%s,%lu,%d,%.3f,%.3f,%.3f,%.3f,%.2f,%zu,%u,%u,%u,%u,%u\n",
r.filename.c_str(), (unsigned long)r.file_size, r.iterations,
r.parse_min, r.parse_max, r.parse_avg, r.parse_median,
r.throughput_mbs, r.arena_peak,
r.meshes, r.nodes, r.accessors, r.materials, r.animations);
}
/* ------------------------------------------------------------------ */
/* Main */
/* ------------------------------------------------------------------ */
static void usage() {
fprintf(stderr,
"Usage:\n"
" bench_v3 <file> [--iterations N] [--warmup N] [--csv] [--quiet]\n"
" bench_v3 --batch <file1> [file2] ... [--iterations N] [--csv]\n"
"\n"
"Options:\n"
" --iterations N Number of timed parse iterations (default: 10)\n"
" --warmup N Number of warmup iterations (default: 2)\n"
" --csv Output in CSV format\n"
" --quiet Suppress per-iteration error messages\n"
" --batch Benchmark multiple files\n"
" --float32 Parse JSON floats as float32 (faster, less precise)\n");
}
int main(int argc, char **argv) {
if (argc < 2) { usage(); return 1; }
int iterations = 10;
int warmup = 2;
bool csv = false;
bool quiet = false;
int float32_mode = 0;
std::vector<std::string> files;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--iterations") == 0 && i + 1 < argc) {
iterations = atoi(argv[++i]);
} else if (strcmp(argv[i], "--warmup") == 0 && i + 1 < argc) {
warmup = atoi(argv[++i]);
} else if (strcmp(argv[i], "--csv") == 0) {
csv = true;
} else if (strcmp(argv[i], "--quiet") == 0) {
quiet = true;
} else if (strcmp(argv[i], "--float32") == 0) {
float32_mode = 1;
} else if (strcmp(argv[i], "--batch") == 0) {
/* batch mode: just collect files */
} else if (argv[i][0] != '-') {
files.push_back(argv[i]);
}
}
if (files.empty()) { usage(); return 1; }
if (csv) print_csv_header();
for (const auto &file : files) {
if (!csv && !quiet) {
printf("Benchmarking: %s (%d iterations, %d warmup%s)\n",
file.c_str(), iterations, warmup,
float32_mode ? ", float32" : "");
}
BenchResult r = bench_file(file.c_str(), iterations, warmup, quiet, float32_mode);
if (csv) {
print_csv_row(r);
} else {
print_result(r);
printf("\n");
}
}
return 0;
}

740
benchmark/gen_synthetic.cpp Normal file
View File

@@ -0,0 +1,740 @@
/*
* gen_synthetic.cpp — Generate synthetic glTF 2.0 scenes for benchmarking.
*
* Produces .gltf (ASCII) and .glb (binary) files with configurable:
* - Number of meshes, each with N vertices/triangles
* - Number of nodes (flat hierarchy)
* - Number of materials
* - Number of animations with M keyframes
*
* Usage:
* gen_synthetic [--meshes N] [--verts-per-mesh N] [--nodes N]
* [--materials N] [--animations N] [--keyframes N]
* [--prefix NAME]
*
* Outputs: <prefix>_<label>.gltf and <prefix>_<label>.glb
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <cstdint>
/* ------------------------------------------------------------------ */
/* Tiny JSON writer (no dependencies) */
/* ------------------------------------------------------------------ */
struct JsonWriter {
std::string buf;
int indent;
bool need_comma;
std::vector<bool> stack; /* true = array context */
JsonWriter() : indent(0), need_comma(false) {}
void comma() {
if (need_comma) buf += ",";
buf += "\n";
for (int i = 0; i < indent; ++i) buf += " ";
}
void begin_obj() {
if (!stack.empty()) comma();
buf += "{";
indent++;
need_comma = false;
stack.push_back(false);
}
void end_obj() {
indent--;
buf += "\n";
for (int i = 0; i < indent; ++i) buf += " ";
buf += "}";
stack.pop_back();
need_comma = true;
}
void begin_arr() {
if (!stack.empty() && !need_comma) { /* first elem */ }
buf += "[";
indent++;
need_comma = false;
stack.push_back(true);
}
void end_arr() {
indent--;
buf += "\n";
for (int i = 0; i < indent; ++i) buf += " ";
buf += "]";
stack.pop_back();
need_comma = true;
}
void key(const char *k) {
comma();
buf += "\"";
buf += k;
buf += "\": ";
need_comma = false;
}
void val_str(const char *v) {
if (stack.back()) comma();
buf += "\"";
buf += v;
buf += "\"";
need_comma = true;
}
void val_int(int64_t v) {
if (stack.back()) comma();
buf += std::to_string(v);
need_comma = true;
}
void val_double(double v) {
if (stack.back()) comma();
char tmp[64];
snprintf(tmp, sizeof(tmp), "%.6g", v);
buf += tmp;
need_comma = true;
}
void val_bool(bool v) {
if (stack.back()) comma();
buf += v ? "true" : "false";
need_comma = true;
}
void kv_str(const char *k, const char *v) { key(k); val_str(v); need_comma = true; }
void kv_int(const char *k, int64_t v) { key(k); val_int(v); need_comma = true; }
void kv_double(const char *k, double v) { key(k); val_double(v); need_comma = true; }
void kv_bool(const char *k, bool v) { key(k); val_bool(v); need_comma = true; }
};
/* ------------------------------------------------------------------ */
/* Binary buffer builder */
/* ------------------------------------------------------------------ */
struct BinBuffer {
std::vector<uint8_t> data;
size_t offset() const { return data.size(); }
void push_float(float v) {
const uint8_t *p = reinterpret_cast<const uint8_t*>(&v);
data.insert(data.end(), p, p + 4);
}
void push_u16(uint16_t v) {
const uint8_t *p = reinterpret_cast<const uint8_t*>(&v);
data.insert(data.end(), p, p + 2);
}
void push_u32(uint32_t v) {
const uint8_t *p = reinterpret_cast<const uint8_t*>(&v);
data.insert(data.end(), p, p + 4);
}
void align4() {
while (data.size() % 4 != 0) data.push_back(0);
}
};
/* ------------------------------------------------------------------ */
/* Scene config */
/* ------------------------------------------------------------------ */
struct SceneConfig {
int num_meshes;
int verts_per_mesh;
int num_nodes;
int num_materials;
int num_animations;
int keyframes;
};
/* ------------------------------------------------------------------ */
/* Generate the scene */
/* ------------------------------------------------------------------ */
struct AccessorInfo {
int buffer_view;
int component_type;
int count;
const char *type;
float min_vals[3];
float max_vals[3];
int min_max_components; /* 0 = none, 1 = scalar, 3 = vec3 */
};
static void generate_scene(const SceneConfig &cfg,
std::string &out_json,
std::vector<uint8_t> &out_bin) {
BinBuffer bin;
/* Pre-compute sizes */
int tris_per_mesh = cfg.verts_per_mesh / 3;
if (tris_per_mesh < 1) tris_per_mesh = 1;
int actual_verts = tris_per_mesh * 3;
/*
* For each mesh:
* - positions: actual_verts * 3 floats
* - normals: actual_verts * 3 floats
* - indices: tris_per_mesh * 3 uint16 (or uint32 if >65535)
*
* For each animation:
* - time keys: keyframes floats
* - translation values: keyframes * 3 floats
*/
/* Track buffer views and accessors */
std::vector<size_t> bv_offsets;
std::vector<size_t> bv_lengths;
std::vector<AccessorInfo> accessors;
int bv_idx = 0;
bool use_u32_indices = (actual_verts > 65535);
/* === Mesh data === */
for (int m = 0; m < cfg.num_meshes; ++m) {
float mesh_offset_x = (float)m * 5.0f;
/* Positions */
size_t pos_off = bin.offset();
float pmin[3] = {1e30f, 1e30f, 1e30f};
float pmax[3] = {-1e30f, -1e30f, -1e30f};
for (int v = 0; v < actual_verts; ++v) {
float angle = (float)v / (float)actual_verts * 6.2831853f;
float r = 1.0f + 0.3f * sinf(angle * 5.0f);
float x = mesh_offset_x + r * cosf(angle);
float y = r * sinf(angle);
float z = 0.5f * sinf(angle * 3.0f + (float)m);
bin.push_float(x); bin.push_float(y); bin.push_float(z);
if (x < pmin[0]) pmin[0] = x;
if (x > pmax[0]) pmax[0] = x;
if (y < pmin[1]) pmin[1] = y;
if (y > pmax[1]) pmax[1] = y;
if (z < pmin[2]) pmin[2] = z;
if (z > pmax[2]) pmax[2] = z;
}
size_t pos_len = bin.offset() - pos_off;
bin.align4();
bv_offsets.push_back(pos_off); bv_lengths.push_back(pos_len);
int pos_bv = bv_idx++;
AccessorInfo pos_acc;
pos_acc.buffer_view = pos_bv;
pos_acc.component_type = 5126; /* FLOAT */
pos_acc.count = actual_verts;
pos_acc.type = "VEC3";
memcpy(pos_acc.min_vals, pmin, sizeof(pmin));
memcpy(pos_acc.max_vals, pmax, sizeof(pmax));
pos_acc.min_max_components = 3;
accessors.push_back(pos_acc);
/* Normals */
size_t norm_off = bin.offset();
for (int v = 0; v < actual_verts; ++v) {
float angle = (float)v / (float)actual_verts * 6.2831853f;
float nx = cosf(angle), ny = sinf(angle), nz = 0.0f;
float len = sqrtf(nx*nx + ny*ny + nz*nz);
if (len > 0) { nx /= len; ny /= len; nz /= len; }
bin.push_float(nx); bin.push_float(ny); bin.push_float(nz);
}
size_t norm_len = bin.offset() - norm_off;
bin.align4();
bv_offsets.push_back(norm_off); bv_lengths.push_back(norm_len);
int norm_bv = bv_idx++;
AccessorInfo norm_acc;
norm_acc.buffer_view = norm_bv;
norm_acc.component_type = 5126;
norm_acc.count = actual_verts;
norm_acc.type = "VEC3";
norm_acc.min_max_components = 0;
accessors.push_back(norm_acc);
/* Indices */
size_t idx_off = bin.offset();
for (int t = 0; t < tris_per_mesh; ++t) {
if (use_u32_indices) {
bin.push_u32((uint32_t)(t * 3));
bin.push_u32((uint32_t)(t * 3 + 1));
bin.push_u32((uint32_t)(t * 3 + 2));
} else {
bin.push_u16((uint16_t)(t * 3));
bin.push_u16((uint16_t)(t * 3 + 1));
bin.push_u16((uint16_t)(t * 3 + 2));
}
}
size_t idx_len = bin.offset() - idx_off;
bin.align4();
bv_offsets.push_back(idx_off); bv_lengths.push_back(idx_len);
int idx_bv = bv_idx++;
AccessorInfo idx_acc;
idx_acc.buffer_view = idx_bv;
idx_acc.component_type = use_u32_indices ? 5125 : 5123; /* UINT or USHORT */
idx_acc.count = tris_per_mesh * 3;
idx_acc.type = "SCALAR";
idx_acc.min_max_components = 0;
accessors.push_back(idx_acc);
}
/* === Animation data === */
int anim_time_accessor_start = (int)accessors.size();
for (int a = 0; a < cfg.num_animations; ++a) {
/* Time keys */
size_t time_off = bin.offset();
float tmin = 0.0f, tmax = 0.0f;
for (int k = 0; k < cfg.keyframes; ++k) {
float t = (float)k / (float)(cfg.keyframes - 1) * 10.0f;
bin.push_float(t);
if (k == 0) tmin = t;
tmax = t;
}
size_t time_len = bin.offset() - time_off;
bin.align4();
bv_offsets.push_back(time_off); bv_lengths.push_back(time_len);
int time_bv = bv_idx++;
AccessorInfo time_acc;
time_acc.buffer_view = time_bv;
time_acc.component_type = 5126;
time_acc.count = cfg.keyframes;
time_acc.type = "SCALAR";
time_acc.min_vals[0] = tmin;
time_acc.max_vals[0] = tmax;
time_acc.min_max_components = 1;
accessors.push_back(time_acc);
/* Translation values */
size_t val_off = bin.offset();
for (int k = 0; k < cfg.keyframes; ++k) {
float t = (float)k / (float)(cfg.keyframes - 1) * 10.0f;
float x = sinf(t * 0.5f + (float)a) * 2.0f;
float y = cosf(t * 0.3f) * 1.5f;
float z = sinf(t * 0.7f + (float)a * 0.5f);
bin.push_float(x); bin.push_float(y); bin.push_float(z);
}
size_t val_len = bin.offset() - val_off;
bin.align4();
bv_offsets.push_back(val_off); bv_lengths.push_back(val_len);
int val_bv = bv_idx++;
AccessorInfo val_acc;
val_acc.buffer_view = val_bv;
val_acc.component_type = 5126;
val_acc.count = cfg.keyframes;
val_acc.type = "VEC3";
val_acc.min_max_components = 0;
accessors.push_back(val_acc);
}
size_t total_bin = bin.data.size();
/* === Build JSON === */
JsonWriter w;
w.begin_obj();
/* asset */
w.key("asset"); w.begin_obj();
w.kv_str("version", "2.0");
w.kv_str("generator", "tinygltf_benchmark_gen");
w.end_obj();
/* scene */
w.kv_int("scene", 0);
/* scenes */
w.key("scenes"); w.begin_arr();
w.begin_obj();
w.kv_str("name", "BenchmarkScene");
w.key("nodes"); w.begin_arr();
for (int n = 0; n < cfg.num_nodes; ++n) w.val_int(n);
w.end_arr();
w.end_obj();
w.end_arr();
/* nodes */
w.key("nodes"); w.begin_arr();
for (int n = 0; n < cfg.num_nodes; ++n) {
w.begin_obj();
w.kv_str("name", ("Node_" + std::to_string(n)).c_str());
if (n < cfg.num_meshes) {
w.kv_int("mesh", n);
}
w.key("translation"); w.begin_arr();
w.val_double((double)n * 3.0);
w.val_double(0.0);
w.val_double(0.0);
w.end_arr();
w.end_obj();
}
w.end_arr();
/* meshes */
w.key("meshes"); w.begin_arr();
for (int m = 0; m < cfg.num_meshes; ++m) {
int base_acc = m * 3; /* pos, norm, idx per mesh */
w.begin_obj();
w.kv_str("name", ("Mesh_" + std::to_string(m)).c_str());
w.key("primitives"); w.begin_arr();
w.begin_obj();
w.key("attributes"); w.begin_obj();
w.kv_int("POSITION", base_acc);
w.kv_int("NORMAL", base_acc + 1);
w.end_obj();
w.kv_int("indices", base_acc + 2);
w.kv_int("material", m % cfg.num_materials);
w.kv_int("mode", 4);
w.end_obj();
w.end_arr();
w.end_obj();
}
w.end_arr();
/* materials */
w.key("materials"); w.begin_arr();
for (int m = 0; m < cfg.num_materials; ++m) {
w.begin_obj();
w.kv_str("name", ("Material_" + std::to_string(m)).c_str());
w.key("pbrMetallicRoughness"); w.begin_obj();
w.key("baseColorFactor"); w.begin_arr();
float hue = (float)m / (float)cfg.num_materials;
w.val_double(0.5 + 0.5 * sin(hue * 6.28));
w.val_double(0.5 + 0.5 * sin(hue * 6.28 + 2.09));
w.val_double(0.5 + 0.5 * sin(hue * 6.28 + 4.19));
w.val_double(1.0);
w.end_arr();
w.kv_double("metallicFactor", 0.2 + 0.6 * ((double)m / cfg.num_materials));
w.kv_double("roughnessFactor", 0.3 + 0.5 * ((double)(cfg.num_materials - m) / cfg.num_materials));
w.end_obj();
w.end_obj();
}
w.end_arr();
/* accessors */
w.key("accessors"); w.begin_arr();
for (size_t i = 0; i < accessors.size(); ++i) {
const AccessorInfo &a = accessors[i];
w.begin_obj();
w.kv_int("bufferView", a.buffer_view);
w.kv_int("componentType", a.component_type);
w.kv_int("count", a.count);
w.kv_str("type", a.type);
if (a.min_max_components == 1) {
w.key("min"); w.begin_arr(); w.val_double(a.min_vals[0]); w.end_arr();
w.key("max"); w.begin_arr(); w.val_double(a.max_vals[0]); w.end_arr();
} else if (a.min_max_components == 3) {
w.key("min"); w.begin_arr();
w.val_double(a.min_vals[0]); w.val_double(a.min_vals[1]); w.val_double(a.min_vals[2]);
w.end_arr();
w.key("max"); w.begin_arr();
w.val_double(a.max_vals[0]); w.val_double(a.max_vals[1]); w.val_double(a.max_vals[2]);
w.end_arr();
}
w.end_obj();
}
w.end_arr();
/* bufferViews */
w.key("bufferViews"); w.begin_arr();
for (int i = 0; i < bv_idx; ++i) {
w.begin_obj();
w.kv_int("buffer", 0);
w.kv_int("byteOffset", (int64_t)bv_offsets[i]);
w.kv_int("byteLength", (int64_t)bv_lengths[i]);
w.end_obj();
}
w.end_arr();
/* buffers */
w.key("buffers"); w.begin_arr();
w.begin_obj();
w.kv_int("byteLength", (int64_t)total_bin);
/* URI will be set by caller for .gltf, omitted for .glb */
w.end_obj();
w.end_arr();
/* animations */
if (cfg.num_animations > 0) {
w.key("animations"); w.begin_arr();
for (int a = 0; a < cfg.num_animations; ++a) {
int time_acc = anim_time_accessor_start + a * 2;
int val_acc = time_acc + 1;
/* Target node: cycle through available nodes */
int target_node = a % cfg.num_nodes;
w.begin_obj();
w.kv_str("name", ("Anim_" + std::to_string(a)).c_str());
w.key("channels"); w.begin_arr();
w.begin_obj();
w.kv_int("sampler", 0);
w.key("target"); w.begin_obj();
w.kv_int("node", target_node);
w.kv_str("path", "translation");
w.end_obj();
w.end_obj();
w.end_arr();
w.key("samplers"); w.begin_arr();
w.begin_obj();
w.kv_int("input", time_acc);
w.kv_int("output", val_acc);
w.kv_str("interpolation", "LINEAR");
w.end_obj();
w.end_arr();
w.end_obj();
}
w.end_arr();
}
w.end_obj();
out_json = w.buf;
out_bin = bin.data;
}
/* ------------------------------------------------------------------ */
/* Write .gltf + .bin */
/* ------------------------------------------------------------------ */
static void write_gltf(const std::string &prefix, const std::string &label,
const std::string &json_str,
const std::vector<uint8_t> &bin_data) {
std::string bin_name = prefix + "_" + label + ".bin";
std::string gltf_name = prefix + "_" + label + ".gltf";
/* Inject "uri" into the buffer object in JSON */
std::string json_patched = json_str;
/* Find the buffers array and add uri before the closing } of the buffer */
size_t pos = json_patched.find("\"byteLength\"");
if (pos != std::string::npos) {
/* Find the line end after byteLength value */
size_t line_end = json_patched.find('\n', pos);
if (line_end != std::string::npos) {
/* Extract just the filename for uri */
std::string bin_filename = prefix + "_" + label + ".bin";
std::string uri_line = ",\n \"uri\": \"" + bin_filename + "\"";
json_patched.insert(line_end, uri_line);
}
}
/* Write .bin */
FILE *f = fopen(bin_name.c_str(), "wb");
if (f) {
fwrite(bin_data.data(), 1, bin_data.size(), f);
fclose(f);
}
/* Write .gltf */
f = fopen(gltf_name.c_str(), "w");
if (f) {
fwrite(json_patched.c_str(), 1, json_patched.size(), f);
fclose(f);
}
printf(" Written: %s (%zu bytes JSON) + %s (%zu bytes binary)\n",
gltf_name.c_str(), json_patched.size(),
bin_name.c_str(), bin_data.size());
}
/* ------------------------------------------------------------------ */
/* Write .glb */
/* ------------------------------------------------------------------ */
static void write_glb(const std::string &prefix, const std::string &label,
const std::string &json_str,
const std::vector<uint8_t> &bin_data) {
std::string glb_name = prefix + "_" + label + ".glb";
uint32_t json_len = (uint32_t)json_str.size();
uint32_t json_padded = (json_len + 3) & ~3u;
uint32_t bin_len = (uint32_t)bin_data.size();
uint32_t bin_padded = (bin_len + 3) & ~3u;
uint32_t total = 12 + 8 + json_padded + 8 + bin_padded;
FILE *f = fopen(glb_name.c_str(), "wb");
if (!f) return;
/* Header */
fwrite("glTF", 1, 4, f);
uint32_t version = 2;
fwrite(&version, 4, 1, f);
fwrite(&total, 4, 1, f);
/* JSON chunk */
uint32_t json_type = 0x4E4F534A;
fwrite(&json_padded, 4, 1, f);
fwrite(&json_type, 4, 1, f);
fwrite(json_str.c_str(), 1, json_len, f);
for (uint32_t i = json_len; i < json_padded; ++i) {
char sp = ' ';
fwrite(&sp, 1, 1, f);
}
/* BIN chunk */
uint32_t bin_type = 0x004E4942;
fwrite(&bin_padded, 4, 1, f);
fwrite(&bin_type, 4, 1, f);
fwrite(bin_data.data(), 1, bin_len, f);
for (uint32_t i = bin_len; i < bin_padded; ++i) {
char z = 0;
fwrite(&z, 1, 1, f);
}
fclose(f);
printf(" Written: %s (%u bytes)\n", glb_name.c_str(), total);
}
/* ------------------------------------------------------------------ */
/* Preset configurations */
/* ------------------------------------------------------------------ */
struct Preset {
const char *label;
SceneConfig cfg;
};
static Preset presets[] = {
{"tiny", {1, 100, 2, 1, 0, 0}},
{"small", {5, 1000, 10, 3, 2, 50}},
{"medium", {20, 5000, 50, 10, 5, 200}},
{"large", {100, 10000, 200, 20, 10, 500}},
{"huge", {500, 50000, 1000, 50, 50, 1000}},
};
static const int num_presets = (int)(sizeof(presets) / sizeof(presets[0]));
/* ------------------------------------------------------------------ */
/* Generate float-heavy scene (~500MB of ASCII float values in JSON) */
/* ------------------------------------------------------------------ */
static void generate_float_heavy(const std::string &prefix, size_t target_mb) {
std::string gltf_name = prefix + "_float_heavy.gltf";
FILE *f = fopen(gltf_name.c_str(), "w");
if (!f) {
fprintf(stderr, "Cannot open %s\n", gltf_name.c_str());
return;
}
/* Write minimal valid glTF with massive extras float array */
fprintf(f, "{\n");
fprintf(f, " \"asset\": {\"version\": \"2.0\", \"generator\": \"tinygltf_benchmark_gen\"},\n");
fprintf(f, " \"scene\": 0,\n");
fprintf(f, " \"scenes\": [{\"name\": \"FloatHeavy\", \"nodes\": [0]}],\n");
fprintf(f, " \"nodes\": [{\"name\": \"Root\"}],\n");
fprintf(f, " \"extras\": {\n");
size_t target_bytes = target_mb * 1024ULL * 1024ULL;
size_t total_written = 0;
int num_channels = 10;
size_t per_channel = target_bytes / (size_t)num_channels;
for (int ch = 0; ch < num_channels; ++ch) {
fprintf(f, " \"channel_%d\": [\n ", ch);
size_t ch_written = 0;
size_t count = 0;
uint64_t seed = (uint64_t)ch * 7919ULL + 1;
bool first = true;
while (ch_written < per_channel) {
/* Comma before every value except the first */
if (!first) {
fwrite(",\n ", 1, 8, f);
ch_written += 8;
}
first = false;
/* Generate varied float values: mix of magnitudes and precisions */
seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
double raw = (double)(int64_t)seed / (double)INT64_MAX;
double val;
int kind = (int)(count % 5);
switch (kind) {
case 0: val = raw * 1000.0; break; /* large: -999.xxx */
case 1: val = raw * 0.001; break; /* small: 0.000xxx */
case 2: val = raw * 3.14159265358979; break; /* medium: -3.14..3.14 */
case 3: val = raw * 1e6; break; /* very large */
case 4: val = raw * 1e-6; break; /* very small */
default: val = raw; break;
}
char buf[64];
int len = snprintf(buf, sizeof(buf), "%.8g", val);
fwrite(buf, 1, (size_t)len, f);
ch_written += (size_t)len;
count++;
}
total_written += ch_written;
if (ch < num_channels - 1) {
fprintf(f, "\n ],\n");
} else {
fprintf(f, "\n ]\n");
}
}
fprintf(f, " }\n");
fprintf(f, "}\n");
fclose(f);
/* Report actual file size */
f = fopen(gltf_name.c_str(), "rb");
if (f) {
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fclose(f);
printf(" Written: %s (%.1f MB, ~%zu float values across %d channels)\n",
gltf_name.c_str(), (double)sz / (1024.0 * 1024.0),
total_written / 12, num_channels);
}
}
/* ------------------------------------------------------------------ */
/* Main */
/* ------------------------------------------------------------------ */
int main(int argc, char **argv) {
std::string prefix = "synthetic";
/* Parse args */
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--prefix") == 0 && i + 1 < argc) {
prefix = argv[++i];
}
}
printf("Generating synthetic glTF benchmark scenes...\n\n");
for (int p = 0; p < num_presets; ++p) {
const Preset &pr = presets[p];
printf("[%s] meshes=%d verts/mesh=%d nodes=%d materials=%d "
"animations=%d keyframes=%d\n",
pr.label, pr.cfg.num_meshes, pr.cfg.verts_per_mesh,
pr.cfg.num_nodes, pr.cfg.num_materials,
pr.cfg.num_animations, pr.cfg.keyframes);
std::string json;
std::vector<uint8_t> bin;
generate_scene(pr.cfg, json, bin);
write_gltf(prefix, pr.label, json, bin);
write_glb(prefix, pr.label, json, bin);
printf("\n");
}
/* Float-heavy scene: ~500MB of ASCII floats in JSON */
printf("[float_heavy] ~500MB of ASCII float values in JSON extras\n");
generate_float_heavy(prefix, 500);
printf("\n");
printf("Done.\n");
return 0;
}

View File

@@ -0,0 +1,5 @@
include_directories(${CMAKE_SOURCE_DIR})
add_executable(create_triangle_gltf create_triangle_gltf.cpp)
target_compile_options(create_triangle_gltf PUBLIC -Wall)
target_link_libraries(create_triangle_gltf )

View File

@@ -788,8 +788,10 @@ static void QuatToAngleAxis(const std::vector<double> quaternion,
return;
}
constexpr double pi = 3.14159265358979323846;
double denom = sqrt(1-qw*qw);
outAngleDegrees = angleRadians * 180.0 / M_PI;
outAngleDegrees = angleRadians * 180.0 / pi;
axis[0] = qx / denom;
axis[1] = qy / denom;
axis[2] = qz / denom;

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* stb_image_write - v1.11 - public domain - http://nothings.org/stb/stb_image_write.h
/* stb_image_write - v1.16 - public domain - http://nothings.org/stb
writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015
no warranty implied; use at your own risk
@@ -10,11 +10,6 @@
Will probably not work correctly with strict-aliasing optimizations.
If using a modern Microsoft Compiler, non-safe versions of CRT calls may cause
compilation warnings or even errors. To avoid this, also before #including,
#define STBI_MSC_SECURE_CRT
ABOUT:
This header file is a library for writing images to C stdio or a callback.
@@ -110,7 +105,7 @@ USAGE:
TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed
data, set the global variable 'stbi_write_tga_with_rle' to 0.
JPEG does ignore alpha channels in input data; quality is between 1 and 100.
Higher quality looks better but results in a bigger image.
JPEG baseline (no JPEG progressive).
@@ -118,7 +113,7 @@ USAGE:
CREDITS:
Sean Barrett - PNG/BMP/TGA
Sean Barrett - PNG/BMP/TGA
Baldur Karlsson - HDR
Jean-Sebastien Guay - TGA monochrome
Tim Kelsey - misc enhancements
@@ -145,6 +140,7 @@ CREDITS:
Ivan Tikhonov
github:ignotion
Adam Schackart
Andrew Kensler
LICENSE
@@ -171,9 +167,9 @@ LICENSE
#endif
#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations
extern int stbi_write_tga_with_rle;
extern int stbi_write_png_compression_level;
extern int stbi_write_force_png_filter;
STBIWDEF int stbi_write_tga_with_rle;
STBIWDEF int stbi_write_png_compression_level;
STBIWDEF int stbi_write_force_png_filter;
#endif
#ifndef STBI_WRITE_NO_STDIO
@@ -183,7 +179,7 @@ STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const
STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality);
#ifdef STBI_WINDOWS_UTF8
#ifdef STBIW_WINDOWS_UTF8
STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
#endif
#endif
@@ -252,17 +248,17 @@ STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean);
#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff)
#ifdef STB_IMAGE_WRITE_STATIC
static int stbi__flip_vertically_on_write=0;
static int stbi_write_png_compression_level = 8;
static int stbi_write_tga_with_rle = 1;
static int stbi_write_force_png_filter = -1;
#else
int stbi_write_png_compression_level = 8;
int stbi__flip_vertically_on_write=0;
int stbi_write_tga_with_rle = 1;
int stbi_write_force_png_filter = -1;
#endif
static int stbi__flip_vertically_on_write = 0;
STBIWDEF void stbi_flip_vertically_on_write(int flag)
{
stbi__flip_vertically_on_write = flag;
@@ -272,6 +268,8 @@ typedef struct
{
stbi_write_func *func;
void *context;
unsigned char buffer[64];
int buf_used;
} stbi__write_context;
// initialize a callback-based context
@@ -288,7 +286,7 @@ static void stbi__stdio_write(void *context, void *data, int size)
fwrite(data,1,size,(FILE*) context);
}
#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)
#ifdef __cplusplus
#define STBIW_EXTERN extern "C"
#else
@@ -299,25 +297,25 @@ STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned in
STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
{
return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, bufferlen, NULL, NULL);
return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
}
#endif
static FILE *stbiw__fopen(char const *filename, char const *mode)
{
FILE *f;
#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)
wchar_t wMode[64];
wchar_t wFilename[1024];
if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)))
return 0;
if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)))
if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename)))
return 0;
#if _MSC_VER >= 1400
if (0 != _wfopen_s(&f, wFilename, wMode))
f = 0;
if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))
return 0;
#if defined(_MSC_VER) && _MSC_VER >= 1400
if (0 != _wfopen_s(&f, wFilename, wMode))
f = 0;
#else
f = _wfopen(wFilename, wMode);
#endif
@@ -385,16 +383,36 @@ static void stbiw__writef(stbi__write_context *s, const char *fmt, ...)
va_end(v);
}
static void stbiw__write_flush(stbi__write_context *s)
{
if (s->buf_used) {
s->func(s->context, &s->buffer, s->buf_used);
s->buf_used = 0;
}
}
static void stbiw__putc(stbi__write_context *s, unsigned char c)
{
s->func(s->context, &c, 1);
}
static void stbiw__write1(stbi__write_context *s, unsigned char a)
{
if ((size_t)s->buf_used + 1 > sizeof(s->buffer))
stbiw__write_flush(s);
s->buffer[s->buf_used++] = a;
}
static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c)
{
unsigned char arr[3];
arr[0] = a, arr[1] = b, arr[2] = c;
s->func(s->context, arr, 3);
int n;
if ((size_t)s->buf_used + 3 > sizeof(s->buffer))
stbiw__write_flush(s);
n = s->buf_used;
s->buf_used = n+3;
s->buffer[n+0] = a;
s->buffer[n+1] = b;
s->buffer[n+2] = c;
}
static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d)
@@ -403,7 +421,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in
int k;
if (write_alpha < 0)
s->func(s->context, &d[comp - 1], 1);
stbiw__write1(s, d[comp - 1]);
switch (comp) {
case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case
@@ -411,7 +429,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in
if (expand_mono)
stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp
else
s->func(s->context, d, 1); // monochrome TGA
stbiw__write1(s, d[0]); // monochrome TGA
break;
case 4:
if (!write_alpha) {
@@ -427,7 +445,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in
break;
}
if (write_alpha > 0)
s->func(s->context, &d[comp - 1], 1);
stbiw__write1(s, d[comp - 1]);
}
static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono)
@@ -441,16 +459,18 @@ static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, i
if (stbi__flip_vertically_on_write)
vdir *= -1;
if (vdir < 0)
j_end = -1, j = y-1;
else
j_end = y, j = 0;
if (vdir < 0) {
j_end = -1; j = y-1;
} else {
j_end = y; j = 0;
}
for (; j != j_end; j += vdir) {
for (i=0; i < x; ++i) {
unsigned char *d = (unsigned char *) data + (j*x+i)*comp;
stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d);
}
stbiw__write_flush(s);
s->func(s->context, &zero, scanline_pad);
}
}
@@ -471,16 +491,27 @@ static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x,
static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data)
{
int pad = (-x*3) & 3;
return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad,
"11 4 22 4" "4 44 22 444444",
'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
if (comp != 4) {
// write RGB bitmap
int pad = (-x*3) & 3;
return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad,
"11 4 22 4" "4 44 22 444444",
'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header
40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header
} else {
// RGBA bitmaps need a v4 header
// use BI_BITFIELDS mode with 32bpp and alpha mask
// (straight BI_RGB with alpha mask doesn't work in most readers)
return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0,
"11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444",
'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header
108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header
}
}
STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
stbi__start_write_callbacks(&s, func, context);
return stbi_write_bmp_core(&s, x, y, comp, data);
}
@@ -488,7 +519,7 @@ STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x,
#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
if (stbi__start_write_file(&s,filename)) {
int r = stbi_write_bmp_core(&s, x, y, comp, data);
stbi__end_write_file(&s);
@@ -561,24 +592,25 @@ static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, v
if (diff) {
unsigned char header = STBIW_UCHAR(len - 1);
s->func(s->context, &header, 1);
stbiw__write1(s, header);
for (k = 0; k < len; ++k) {
stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp);
}
} else {
unsigned char header = STBIW_UCHAR(len - 129);
s->func(s->context, &header, 1);
stbiw__write1(s, header);
stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin);
}
}
}
stbiw__write_flush(s);
}
return 1;
}
STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
stbi__start_write_callbacks(&s, func, context);
return stbi_write_tga_core(&s, x, y, comp, (void *) data);
}
@@ -586,7 +618,7 @@ STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x,
#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
if (stbi__start_write_file(&s,filename)) {
int r = stbi_write_tga_core(&s, x, y, comp, (void *) data);
stbi__end_write_file(&s);
@@ -602,6 +634,8 @@ STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const
#define stbiw__max(a, b) ((a) > (b) ? (a) : (b))
#ifndef STBI_WRITE_NO_STDIO
static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear)
{
int exponent;
@@ -736,10 +770,10 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f
char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n";
s->func(s->context, header, sizeof(header)-1);
#ifdef STBI_MSC_SECURE_CRT
len = sprintf_s(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#ifdef __STDC_LIB_EXT1__
len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#else
len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
len = snprintf(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#endif
s->func(s->context, buffer, len);
@@ -752,15 +786,14 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f
STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
stbi__start_write_callbacks(&s, func, context);
return stbi_write_hdr_core(&s, x, y, comp, (float *) data);
}
#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data)
{
stbi__write_context s;
stbi__write_context s = { 0 };
if (stbi__start_write_file(&s,filename)) {
int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data);
stbi__end_write_file(&s);
@@ -778,7 +811,7 @@ STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const
#ifndef STBIW_ZLIB_COMPRESS
// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size()
#define stbiw__sbraw(a) ((int *) (a) - 2)
#define stbiw__sbraw(a) ((int *) (void *) (a) - 2)
#define stbiw__sbm(a) stbiw__sbraw(a)[0]
#define stbiw__sbn(a) stbiw__sbraw(a)[1]
@@ -872,7 +905,7 @@ STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, i
unsigned int bitbuf=0;
int i,j, bitcount=0;
unsigned char *out = NULL;
unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**));
unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**));
if (hash_table == NULL)
return NULL;
if (quality < 5) quality = 5;
@@ -895,7 +928,7 @@ STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, i
for (j=0; j < n; ++j) {
if (hlist[j]-data > i-32768) { // if entry lies within window
int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i);
if (d >= best) best=d,bestloc=hlist[j];
if (d >= best) { best=d; bestloc=hlist[j]; }
}
}
// when hash table entry is too long, delete half the entries
@@ -948,14 +981,31 @@ STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, i
(void) stbiw__sbfree(hash_table[i]);
STBIW_FREE(hash_table);
// store uncompressed instead if compression was worse
if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) {
stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1
for (j = 0; j < data_len;) {
int blocklen = data_len - j;
if (blocklen > 32767) blocklen = 32767;
stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression
stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN
stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8));
stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN
stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8));
memcpy(out+stbiw__sbn(out), data+j, blocklen);
stbiw__sbn(out) += blocklen;
j += blocklen;
}
}
{
// compute adler32 on input
unsigned int s1=1, s2=0;
int blocklen = (int) (data_len % 5552);
j=0;
while (j < data_len) {
for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1;
s1 %= 65521, s2 %= 65521;
for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; }
s1 %= 65521; s2 %= 65521;
j += blocklen;
blocklen = 5552;
}
@@ -1048,13 +1098,13 @@ static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int
int type = mymap[filter_type];
unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y);
int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes;
if (type==0) {
memcpy(line_buffer, z, width*n);
return;
}
// first loop isn't optimized since it's just one pixel
// first loop isn't optimized since it's just one pixel
for (i = 0; i < n; ++i) {
switch (type) {
case 1: line_buffer[i] = z[i]; break;
@@ -1275,26 +1325,31 @@ static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) {
bits[0] = val & ((1<<bits[1])-1);
}
static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, int du_stride, float *fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) {
const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] };
const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] };
int dataOff, i, diff, end0pos;
int dataOff, i, j, n, diff, end0pos, x, y;
int DU[64];
// DCT rows
for(dataOff=0; dataOff<64; dataOff+=8) {
for(dataOff=0, n=du_stride*8; dataOff<n; dataOff+=du_stride) {
stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+1], &CDU[dataOff+2], &CDU[dataOff+3], &CDU[dataOff+4], &CDU[dataOff+5], &CDU[dataOff+6], &CDU[dataOff+7]);
}
// DCT columns
for(dataOff=0; dataOff<8; ++dataOff) {
stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+8], &CDU[dataOff+16], &CDU[dataOff+24], &CDU[dataOff+32], &CDU[dataOff+40], &CDU[dataOff+48], &CDU[dataOff+56]);
stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+du_stride], &CDU[dataOff+du_stride*2], &CDU[dataOff+du_stride*3], &CDU[dataOff+du_stride*4],
&CDU[dataOff+du_stride*5], &CDU[dataOff+du_stride*6], &CDU[dataOff+du_stride*7]);
}
// Quantize/descale/zigzag the coefficients
for(i=0; i<64; ++i) {
float v = CDU[i]*fdtbl[i];
// DU[stbiw__jpg_ZigZag[i]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
// ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway?
DU[stbiw__jpg_ZigZag[i]] = (int)(v < 0 ? v - 0.5f : v + 0.5f);
for(y = 0, j=0; y < 8; ++y) {
for(x = 0; x < 8; ++x,++j) {
float v;
i = y*du_stride+x;
v = CDU[i]*fdtbl[j];
// DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
// ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway?
DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? v - 0.5f : v + 0.5f);
}
}
// Encode DC
@@ -1409,10 +1464,10 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99};
static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,
99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99};
static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f,
static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f,
1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f };
int row, col, i, k;
int row, col, i, k, subsample;
float fdtbl_Y[64], fdtbl_UV[64];
unsigned char YTable[64], UVTable[64];
@@ -1421,6 +1476,7 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
}
quality = quality ? quality : 90;
subsample = quality <= 90 ? 1 : 0;
quality = quality < 1 ? 1 : quality > 100 ? 100 : quality;
quality = quality < 50 ? 5000 / quality : 200 - quality * 2;
@@ -1443,7 +1499,7 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 };
static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 };
const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width),
3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
s->func(s->context, (void*)head0, sizeof(head0));
s->func(s->context, (void*)YTable, sizeof(YTable));
stbiw__putc(s, 1);
@@ -1466,36 +1522,74 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
// Encode 8x8 macroblocks
{
static const unsigned short fillBits[] = {0x7F, 7};
const unsigned char *imageData = (const unsigned char *)data;
int DCY=0, DCU=0, DCV=0;
int bitBuf=0, bitCnt=0;
// comp == 2 is grey+alpha (alpha is ignored)
int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0;
const unsigned char *dataR = (const unsigned char *)data;
const unsigned char *dataG = dataR + ofsG;
const unsigned char *dataB = dataR + ofsB;
int x, y, pos;
for(y = 0; y < height; y += 8) {
for(x = 0; x < width; x += 8) {
float YDU[64], UDU[64], VDU[64];
for(row = y, pos = 0; row < y+8; ++row) {
// row >= height => use last input row
int clamped_row = (row < height) ? row : height - 1;
int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
for(col = x; col < x+8; ++col, ++pos) {
float r, g, b;
// if col >= width => use pixel from last input column
int p = base_p + ((col < width) ? col : (width-1))*comp;
if(subsample) {
for(y = 0; y < height; y += 16) {
for(x = 0; x < width; x += 16) {
float Y[256], U[256], V[256];
for(row = y, pos = 0; row < y+16; ++row) {
// row >= height => use last input row
int clamped_row = (row < height) ? row : height - 1;
int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
for(col = x; col < x+16; ++col, ++pos) {
// if col >= width => use pixel from last input column
int p = base_p + ((col < width) ? col : (width-1))*comp;
float r = dataR[p], g = dataG[p], b = dataB[p];
Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
}
}
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
r = imageData[p+0];
g = imageData[p+ofsG];
b = imageData[p+ofsB];
YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128;
UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b;
VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b;
// subsample U,V
{
float subU[64], subV[64];
int yy, xx;
for(yy = 0, pos = 0; yy < 8; ++yy) {
for(xx = 0; xx < 8; ++xx, ++pos) {
int j = yy*32+xx*2;
subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f;
subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f;
}
}
DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
}
}
}
} else {
for(y = 0; y < height; y += 8) {
for(x = 0; x < width; x += 8) {
float Y[64], U[64], V[64];
for(row = y, pos = 0; row < y+8; ++row) {
// row >= height => use last input row
int clamped_row = (row < height) ? row : height - 1;
int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
for(col = x; col < x+8; ++col, ++pos) {
// if col >= width => use pixel from last input column
int p = base_p + ((col < width) ? col : (width-1))*comp;
float r = dataR[p], g = dataG[p], b = dataB[p];
Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
}
}
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
}
}
}
@@ -1512,7 +1606,7 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in
STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality)
{
stbi__write_context s;
stbi__write_context s = { 0 };
stbi__start_write_callbacks(&s, func, context);
return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality);
}
@@ -1521,7 +1615,7 @@ STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x,
#ifndef STBI_WRITE_NO_STDIO
STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality)
{
stbi__write_context s;
stbi__write_context s = { 0 };
if (stbi__start_write_file(&s,filename)) {
int r = stbi_write_jpg_core(&s, x, y, comp, data, quality);
stbi__end_write_file(&s);
@@ -1534,8 +1628,17 @@ STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const
#endif // STB_IMAGE_WRITE_IMPLEMENTATION
/* Revision history
1.16 (2021-07-11)
make Deflate code emit uncompressed blocks when it would otherwise expand
support writing BMPs with alpha channel
1.15 (2020-07-13) unknown
1.14 (2020-02-02) updated JPEG writer to downsample chroma channels
1.13
1.12
1.11 (2019-08-11)
1.10 (2019-02-07)
support utf8 filenames in Windows; fix warnings and platform ifdefs
support utf8 filenames in Windows; fix warnings and platform ifdefs
1.09 (2018-02-11)
fix typo in zlib quality API, improve STB_I_W_STATIC in C++
1.08 (2018-01-29)
@@ -1566,7 +1669,7 @@ STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const
add HDR output
fix monochrome BMP
0.95 (2014-08-17)
add monochrome TGA output
add monochrome TGA output
0.94 (2014-05-31)
rename private functions to avoid conflicts with stb_image.h
0.93 (2014-05-27)
@@ -1584,38 +1687,38 @@ This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/

1
tests/issue-416.gltf Normal file
View File

@@ -0,0 +1 @@
{"images":[{"uri":"%!QAAAQAAA5"}],"asset":{"version":""}}

BIN
tests/issue-492.glb Normal file

Binary file not shown.

View File

@@ -474,7 +474,7 @@ TEST_CASE("image-uri-spaces", "[issue-236]") {
}
REQUIRE(true == ret);
REQUIRE(err.empty());
REQUIRE(!warn.empty()); // relative image path won't exist in tests/
REQUIRE(warn.empty());
REQUIRE(saved.images.size() == model.images.size());
// The image uri in CubeImageUriMultipleSpaces.gltf is not encoded and
@@ -494,24 +494,23 @@ TEST_CASE("image-uri-spaces", "[issue-236]") {
}
TEST_CASE("serialize-empty-material", "[issue-294]") {
tinygltf::Model m;
tinygltf::Material mat;
mat.pbrMetallicRoughness.baseColorFactor = {1.0f, 1.0f, 1.0f, 1.0f}; // default baseColorFactor
m.materials.push_back(mat);
// Add default constructed material to model
m.materials.push_back({});
// Serialize model to output stream
std::stringstream os;
tinygltf::TinyGLTF ctx;
ctx.WriteGltfSceneToStream(&m, os, false, false);
// use nlohmann json
bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false);
REQUIRE(true == ret);
// Parse serialized model
nlohmann::json j = nlohmann::json::parse(os.str());
// Serialized materials shall hold an empty object that
// represents the default constructed material
REQUIRE(j.find("materials") != j.end());
REQUIRE(j["materials"].is_array());
REQUIRE(1 == j["materials"].size());
REQUIRE(j["materials"][0].is_object());
CHECK(j["materials"][0].is_object());
CHECK(j["materials"][0].empty());
}
TEST_CASE("empty-skeleton-id", "[issue-321]") {
@@ -532,7 +531,8 @@ TEST_CASE("empty-skeleton-id", "[issue-321]") {
std::stringstream os;
ctx.WriteGltfSceneToStream(&model, os, false, false);
ret = ctx.WriteGltfSceneToStream(&model, os, false, false);
REQUIRE(true == ret);
// use nlohmann json
nlohmann::json j = nlohmann::json::parse(os.str());
@@ -634,8 +634,9 @@ TEST_CASE("serialize-const-image", "[issue-394]") {
std::stringstream os;
tinygltf::TinyGLTF ctx;
ctx.WriteGltfSceneToStream(const_cast<const tinygltf::Model *>(&m), os, false,
bool ret = ctx.WriteGltfSceneToStream(const_cast<const tinygltf::Model *>(&m), os, false,
false);
REQUIRE(true == ret);
REQUIRE(m.images[0].uri == i.uri);
// use nlohmann json
@@ -661,10 +662,11 @@ TEST_CASE("serialize-image-callback", "[issue-394]") {
auto writer = [](const std::string *basepath, const std::string *filename,
const tinygltf::Image *image, bool embedImages,
const tinygltf::URICallbacks *uri_cb, std::string *out_uri,
void *user_pointer) -> bool {
const tinygltf::FsCallbacks* fs, const tinygltf::URICallbacks *uri_cb,
std::string *out_uri, void *user_pointer) -> bool {
(void)basepath;
(void)image;
(void)fs;
(void)uri_cb;
REQUIRE(*filename == "foo");
REQUIRE(embedImages == true);
@@ -698,12 +700,13 @@ TEST_CASE("serialize-image-failure", "[issue-394]") {
auto writer = [](const std::string *basepath, const std::string *filename,
const tinygltf::Image *image, bool embedImages,
const tinygltf::URICallbacks *uri_cb, std::string *out_uri,
void *user_pointer) -> bool {
const tinygltf::FsCallbacks* fs, const tinygltf::URICallbacks *uri_cb,
std::string *out_uri, void *user_pointer) -> bool {
(void)basepath;
(void)filename;
(void)image;
(void)embedImages;
(void)fs;
(void)uri_cb;
(void)out_uri;
(void)user_pointer;
@@ -718,3 +721,531 @@ TEST_CASE("serialize-image-failure", "[issue-394]") {
REQUIRE(false == result);
REQUIRE(os.str().size() == 0);
}
TEST_CASE("filesize-check", "[issue-416]") {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
ctx.SetMaxExternalFileSize(10); // 10 bytes. will fail to load texture image.
bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/Cube/Cube.gltf");
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(false == ret);
}
TEST_CASE("load-issue-416-model", "[issue-416]") {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
bool ret = ctx.LoadASCIIFromFile(&model, &err, &warn, "issue-416.gltf");
if (!warn.empty()) {
std::cout << "WARN:" << warn << std::endl;
}
if (!err.empty()) {
std::cerr << "ERR:" << err << std::endl;
}
// external file load fails, but reading glTF itself is ok.
REQUIRE(true == ret);
}
TEST_CASE("serialize-empty-node", "[issue-457]") {
tinygltf::Model m;
// Add default constructed node to model
m.nodes.push_back({});
// Add scene to model
m.scenes.push_back({});
// The scene's only node is the empty node
m.scenes.front().nodes.push_back(0);
// Serialize model to output stream
std::stringstream os;
tinygltf::TinyGLTF ctx;
bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false);
REQUIRE(true == ret);
// Parse serialized model
nlohmann::json j = nlohmann::json::parse(os.str());
// Serialized nodes shall hold an empty object that
// represents the default constructed node
REQUIRE(j.find("nodes") != j.end());
REQUIRE(j["nodes"].is_array());
REQUIRE(1 == j["nodes"].size());
CHECK(j["nodes"][0].is_object());
CHECK(j["nodes"][0].empty());
// We also want to make sure that the serialized scene
// is referencing the empty node.
// There shall be a single serialized scene
auto scenes = j.find("scenes");
REQUIRE(scenes != j.end());
REQUIRE(scenes->is_array());
REQUIRE(1 == scenes->size());
auto scene = scenes->at(0);
REQUIRE(scene.is_object());
// The scene's nodes array shall hold a reference
// to the single node
auto nodes = scene.find("nodes");
REQUIRE(nodes != scene.end());
REQUIRE(nodes->is_array());
REQUIRE(1 == nodes->size());
auto node = nodes->at(0);
CHECK(node.is_number_integer());
int idx = -1;
node.get_to(idx);
CHECK(0 == idx);
}
TEST_CASE("serialize-light-index", "[issue-458]") {
// Create the light
tinygltf::Light light;
light.type = "point";
light.intensity = 0.75;
light.color = std::vector<double>{1.0, 0.8, 0.95};
// Stream to serialize to
std::stringstream os;
{
tinygltf::Model m;
tinygltf::Scene scene;
// Add the light to the model
m.lights.push_back(light);
// Create a node that uses the light
tinygltf::Node node;
node.light = 0;
// Add the node to the model
m.nodes.push_back(node);
// Add the node to the scene
scene.nodes.push_back(0);
// Add the scene to the model
m.scenes.push_back(scene);
// Serialize model to output stream
tinygltf::TinyGLTF ctx;
bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false);
REQUIRE(true == ret);
}
{
tinygltf::Model m;
tinygltf::TinyGLTF ctx;
// Parse the serialized model
bool ok = ctx.LoadASCIIFromString(&m, nullptr, nullptr, os.str().c_str(), os.str().size(), "");
REQUIRE(true == ok);
// Check if the light was correctly serialized
REQUIRE(1 == m.lights.size());
CHECK(m.lights[0] == light);
// Check that the node properly references the light
REQUIRE(1 == m.nodes.size());
CHECK(m.nodes[0].light == 0);
}
}
TEST_CASE("default-material", "[issue-459]") {
const std::vector<double> default_emissive_factor{ 0.0, 0.0, 0.0 };
const std::vector<double> default_base_color_factor{ 1.0, 1.0, 1.0, 1.0 };
const std::string default_alpha_mode = "OPAQUE";
const double default_alpha_cutoff = 0.5;
const bool default_double_sided = false;
const double default_metallic_factor = 1.0;
const double default_roughness_factor = 1.0;
// Check that default constructed material
// holds actual default GLTF material properties
tinygltf::Material mat;
CHECK(mat.alphaMode == default_alpha_mode);
CHECK(mat.alphaCutoff == default_alpha_cutoff);
CHECK(mat.doubleSided == default_double_sided);
CHECK(mat.emissiveFactor == default_emissive_factor);
CHECK(mat.pbrMetallicRoughness.baseColorFactor == default_base_color_factor);
CHECK(mat.pbrMetallicRoughness.metallicFactor == default_metallic_factor);
CHECK(mat.pbrMetallicRoughness.roughnessFactor == default_roughness_factor);
// None of the textures should be set
CHECK(mat.normalTexture.index == -1);
CHECK(mat.occlusionTexture.index == -1);
CHECK(mat.emissiveTexture.index == -1);
}
TEST_CASE("serialize-empty-scene", "[issue-464]") {
// Stream to serialize to
std::stringstream os;
{
tinygltf::Model m;
// Add empty scene to the model
m.scenes.push_back({});
// Serialize model to output stream
tinygltf::TinyGLTF ctx;
bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false);
REQUIRE(true == ret);
}
{
tinygltf::Model m;
tinygltf::TinyGLTF ctx;
// Parse the serialized model
bool ok = ctx.LoadASCIIFromString(&m, nullptr, nullptr, os.str().c_str(), os.str().size(), "");
REQUIRE(true == ok);
// Make sure the empty scene is there
REQUIRE(1 == m.scenes.size());
tinygltf::Scene scene{};
// Check that the scene is empty
CHECK(m.scenes[0] == scene);
}
}
TEST_CASE("zero-sized-bin-chunk-glb", "[issue-440]") {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
// Input glb has zero-sized data in bin chunk(8 bytes for BIN chunk, and chunksize == 0)
// The spec https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#binary-buffer says
//
// When the binary buffer is empty or when it is stored by other means, this chunk SHOULD be omitted.
//
// 'SHOULD' mean 'RECOMMENDED', so we'll need to allow such zero-sized bin chunk is NOT omitted.
bool ret = ctx.LoadBinaryFromFile(&model, &err, &warn, "../models/regression/zero-sized-bin-chunk-issue-440.glb");
if (!warn.empty()) {
std::cout << "WARN: " << warn << "\n";
}
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
}
TEST_CASE("serialize-node-emitter", "[KHR_audio]") {
// Stream to serialize to
std::stringstream os;
{
tinygltf::Model m;
// Create a default audio emitter
m.audioEmitters.resize(1);
// Create a single node
m.nodes.resize(1);
// The node references the single emitter
m.nodes[0].emitter = 0;
// Create a single scene
m.scenes.resize(1);
// Make the scene reference the single node
m.scenes[0].nodes.push_back(0);
// Serialize model to output stream
tinygltf::TinyGLTF ctx;
bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false);
REQUIRE(true == ret);
}
{
tinygltf::Model m;
tinygltf::TinyGLTF ctx;
// Parse the serialized model
bool ok = ctx.LoadASCIIFromString(&m, nullptr, nullptr, os.str().c_str(), os.str().size(), "");
REQUIRE(true == ok);
// Make sure the single scene is there
REQUIRE(1 == m.scenes.size());
// Make sure all three nodes are there
REQUIRE(1 == m.nodes.size());
// Make sure the single root node of the scene is there
REQUIRE(1 == m.scenes[0].nodes.size());
REQUIRE(0 == m.scenes[0].nodes[0]);
// Retrieve the scene root node
const tinygltf::Node& node = m.nodes[m.scenes[0].nodes[0]];
// Make sure the single root node has both lod nodes
REQUIRE(0 == node.emitter);
}
}
TEST_CASE("serialize-lods", "[lods]") {
// Stream to serialize to
std::stringstream os;
{
tinygltf::Model m;
m.nodes.resize(4);
// Add Node 1 and Node 2 as lods to Node 0
m.nodes[0].lods.push_back(1);
m.nodes[0].lods.push_back(2);
// Add Material 1 and Material 2 as lods to Material 0
m.materials.resize(4);
m.materials[0].lods.push_back(1);
m.materials[0].lods.push_back(2);
tinygltf::Scene scene;
// Scene uses Node 0 and 3 as root node
scene.nodes.push_back(0);
scene.nodes.push_back(3);
// Add scene to the model
m.scenes.push_back(scene);
// Serialize model to output stream
tinygltf::TinyGLTF ctx;
bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false);
REQUIRE(true == ret);
}
{
tinygltf::Model m;
tinygltf::TinyGLTF ctx;
// Parse the serialized model
bool ok = ctx.LoadASCIIFromString(&m, nullptr, nullptr, os.str().c_str(), os.str().size(), "");
REQUIRE(true == ok);
// Make sure the model's used extensions hold MSFT_lod
CHECK(m.extensionsUsed.size() == 1);
CHECK(m.extensionsUsed[0].compare("MSFT_lod") == 0);
// MSFT_lod is not a required extension
CHECK(m.extensionsRequired.size() == 0);
// Make sure all four materials are there
REQUIRE(4 == m.materials.size());
// Make sure the first material has both lod materials
REQUIRE(2 == m.materials[0].lods.size());
// Make sure the order is still the same after serialization and deserialization
CHECK(1 == m.materials[0].lods[0]);
CHECK(2 == m.materials[0].lods[1]);
// Make sure the material with lods exposes the MSFT_lod extension
CHECK(m.materials[0].extensions.size() == 1);
CHECK(m.materials[0].extensions.count("MSFT_lod") == 1);
// Make sure the last material has no lod materials
CHECK(0 == m.materials[3].lods.size());
// Make sure the material without lods does not exposes the MSFT_lod extension
CHECK(m.materials[3].extensions.size() == 0);
CHECK(m.materials[3].extensions.count("MSFT_lod") == 0);
// Make sure the single scene is there
REQUIRE(1 == m.scenes.size());
// Make sure all four nodes are there
REQUIRE(4 == m.nodes.size());
// Make sure the two root nodes of the scene are there
REQUIRE(2 == m.scenes[0].nodes.size());
REQUIRE(0 == m.scenes[0].nodes[0]);
REQUIRE(3 == m.scenes[0].nodes[1]);
// Retrieve the node with lods
const tinygltf::Node& nodeWithLods = m.nodes[m.scenes[0].nodes[0]];
// Make sure the node has both lod nodes
REQUIRE(2 == nodeWithLods.lods.size());
// Make sure the order is still the same after serialization and deserialization
CHECK(1 == nodeWithLods.lods[0]);
CHECK(2 == nodeWithLods.lods[1]);
// Make sure the node with lods exposes the MSFT_lod extension
CHECK(nodeWithLods.extensions.size() == 1);
CHECK(nodeWithLods.extensions.count("MSFT_lod") == 1);
// Retrieve the node without lods
const tinygltf::Node& nodeWithoutLods = m.nodes[m.scenes[0].nodes[1]];
// Make sure the node has no lod nodes
CHECK(0 == nodeWithoutLods.lods.size());
// Make sure the node without lods does not exposes the MSFT_lod extension
CHECK(nodeWithoutLods.extensions.size() == 0);
CHECK(nodeWithoutLods.extensions.count("MSFT_lod") == 0);
}
}
TEST_CASE("write-image-issue", "[issue-473]") {
std::string err;
std::string warn;
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
bool ok = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/Cube/Cube.gltf");
REQUIRE(ok);
REQUIRE(err.empty());
REQUIRE(warn.empty());
REQUIRE(model.images.size() == 2);
REQUIRE(model.images[0].uri == "Cube_BaseColor.png");
REQUIRE(model.images[1].uri == "Cube_MetallicRoughness.png");
REQUIRE_FALSE(model.images[0].image.empty());
REQUIRE_FALSE(model.images[1].image.empty());
ok = ctx.WriteGltfSceneToFile(&model, "Cube.gltf");
REQUIRE(ok);
for (const auto& image : model.images) {
std::fstream file(image.uri);
CHECK(file.good());
}
}
TEST_CASE("images-as-is", "[issue-487]") {
std::string err;
std::string warn;
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
ctx.SetImagesAsIs(true);
bool ok = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/Cube/Cube.gltf");
REQUIRE(ok);
REQUIRE(err.empty());
REQUIRE(warn.empty());
for (const auto& image : model.images) {
CHECK(image.as_is == true);
CHECK_FALSE(image.uri.empty());
CHECK_FALSE(image.image.empty());
#ifndef TINYGLTF_NO_STB_IMAGE
// Make sure we can decode the images
int w = -1, h = -1, component = -1;
unsigned char *data = stbi_load_from_memory(image.image.data(), static_cast<int>(image.image.size()), &w, &h, &component, 0);
CHECK(data != nullptr);
CHECK(w == 512);
CHECK(h == 512);
CHECK(component >= 3);
stbi_image_free(data);
#endif
}
// Write glTF model to disk, and images as separate files
{
ok = ctx.WriteGltfSceneToFile(&model, "Cube_with_image_files.gltf");
REQUIRE(ok);
// All the images should have been written to disk with their original data
for (const auto& image : model.images) {
// Make sure the image files exist
{
std::fstream file(image.uri);
CHECK(file.good());
} // Close file before stbi_load (Windows sharing violation fix)
#ifndef TINYGLTF_NO_STB_IMAGE
// Make sure we can load the images
int w = -1, h = -1, component = -1;
unsigned char *data = stbi_load(image.uri.c_str(), &w, &h, &component, 0);
CHECK(data != nullptr);
CHECK(w == 512);
CHECK(h == 512);
CHECK(component >= 3);
stbi_image_free(data);
#endif
}
}
// Write glTF model to disk, and embed images as data URIs
{
ok = ctx.WriteGltfSceneToFile(&model, "Cube_with_embedded_images.gltf", true, false);
REQUIRE(ok);
// Load above model again, and check if the images are loaded properly
tinygltf::Model embeddedImages;
ctx.SetImagesAsIs(false);
bool ok = ctx.LoadASCIIFromFile(&embeddedImages, &err, &warn, "Cube_with_embedded_images.gltf");
REQUIRE(ok);
REQUIRE(err.empty());
REQUIRE(warn.empty());
for (const auto& image : embeddedImages.images) {
CHECK(image.as_is == false);
CHECK_FALSE(image.mimeType.empty());
CHECK_FALSE(image.image.empty());
CHECK(image.width == 512);
CHECK(image.height == 512);
CHECK(image.component >= 3);
}
}
// Write glTF model to disk, as GLB
{
ok = ctx.WriteGltfSceneToFile(&model, "Cube.glb", true, true, true, true);
REQUIRE(ok);
// Load above model again, and check if the images are loaded properly
tinygltf::Model glbModel;
ctx.SetImagesAsIs(false);
bool ok = ctx.LoadBinaryFromFile(&glbModel, &err, &warn, "Cube.glb");
REQUIRE(ok);
REQUIRE(err.empty());
REQUIRE(warn.empty());
for (const auto& image : glbModel.images) {
CHECK(image.as_is == false);
CHECK_FALSE(image.mimeType.empty());
CHECK_FALSE(image.image.empty());
CHECK(image.width == 512);
CHECK(image.height == 512);
CHECK(image.component >= 3);
}
}
}
TEST_CASE("inverse-bind-matrices-optional", "[issue-492]") {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
bool ret = ctx.LoadBinaryFromFile(&model, &err, &warn, "issue-492.glb");
if (!warn.empty()) {
std::cout << "WARN:" << warn << std::endl;
}
if (!err.empty()) {
std::cerr << "ERR:" << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(err.empty());
}
bool LoadImageData(tinygltf::Image * /* image */, const int /* image_idx */, std::string * /* err */,
std::string * /* warn */, int /* req_width */, int /* req_height */,
const unsigned char * /* bytes */, int /* size */, void * /*user_data */) {
return true;
}
bool WriteImageData(const std::string * /* basepath */, const std::string * /* filename */,
const tinygltf::Image *image, bool /* embedImages */,
const tinygltf::FsCallbacks * /* fs_cb */, const tinygltf::URICallbacks * /* uri_cb */,
std::string * /* out_uri */, void * user_pointer) {
REQUIRE(user_pointer != nullptr);
auto counter = static_cast<int*>(user_pointer);
*counter = *counter + 1;
return true;
}
TEST_CASE("empty-images-not-written", "[issue-495]") {
std::string err;
std::string warn;
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
ctx.SetImageLoader(LoadImageData, nullptr);
bool ok = ctx.LoadASCIIFromFile(&model, &err, &warn, "../models/Cube/Cube.gltf");
REQUIRE(ok);
REQUIRE(err.empty());
REQUIRE(warn.empty());
CHECK(model.images.size() == 2);
for (const auto& image : model.images) {
// No data loaded or decoded
CHECK(image.image.empty());
// The URI is kept
CHECK_FALSE(image.uri.empty());
// The URI should not be a data URI
CHECK(image.uri.find("data:") != 0);
}
// Now write the loaded model
int counter = 0;
ctx.SetImageWriter(WriteImageData, &counter);
ok = ctx.WriteGltfSceneToFile(&model, "issue-495-external.gltf");
CHECK(ok);
// WriteImageData should be invoked for both images
CHECK(counter == 2);
}

67
tests/v3/fuzzer/Makefile Normal file
View File

@@ -0,0 +1,67 @@
# tests/v3/fuzzer/Makefile — Build libFuzzer harness for tinygltf v3
#
# Requires: clang++ with libFuzzer support
#
# Targets:
# make — build fuzzer with ASan + UBSan
# make run — run fuzzer with default settings
# make seed — generate seed corpus from test models
# make clean — remove binaries and corpus
CXX = clang++
CXXFLAGS = -g -O1 -std=c++17 -fno-rtti -fno-exceptions
SANITIZE = -fsanitize=fuzzer,address,undefined
INCLUDES = -I../../..
FUZZER = fuzz_gltf_v3
CORPUS = corpus
ARTIFACTS = artifacts
# Fuzzer runtime options
MAX_LEN ?= 65536
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
MAX_TIME ?= 0
.PHONY: all run seed clean
all: $(FUZZER)
$(FUZZER): fuzz_gltf_v3.cc ../../../tiny_gltf_v3.h ../../../tinygltf_json.h
$(CXX) $(CXXFLAGS) $(SANITIZE) $(INCLUDES) -o $@ $<
run: $(FUZZER) | $(CORPUS) $(ARTIFACTS)
./$(FUZZER) $(CORPUS) \
-artifact_prefix=$(ARTIFACTS)/ \
-max_len=$(MAX_LEN) \
-jobs=$(JOBS) \
-workers=$(JOBS) \
$(if $(filter-out 0,$(MAX_TIME)),-max_total_time=$(MAX_TIME))
# Generate seed corpus from existing test models
seed: | $(CORPUS)
@echo "Seeding corpus from test models..."
@for f in ../../../models/Cube/Cube.gltf \
../../../models/Cube/Cube.glb; do \
if [ -f "$$f" ]; then \
cp "$$f" $(CORPUS)/; \
echo " Added: $$f"; \
fi; \
done
@# Add a minimal valid glTF JSON
@echo '{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"name":"n"}]}' > $(CORPUS)/minimal.gltf
@# Add a minimal valid GLB (header + empty JSON chunk)
@printf 'glTF\x02\x00\x00\x00\x1c\x00\x00\x00\x04\x00\x00\x00JSON{} ' > $(CORPUS)/minimal.glb
@# Add edge cases
@echo '{}' > $(CORPUS)/empty_object.gltf
@echo '{"asset":{"version":"2.0"}}' > $(CORPUS)/asset_only.gltf
@echo "Corpus: $$(ls $(CORPUS) | wc -l) files"
$(CORPUS):
mkdir -p $(CORPUS)
$(ARTIFACTS):
mkdir -p $(ARTIFACTS)
clean:
rm -f $(FUZZER)
rm -rf $(CORPUS) $(ARTIFACTS)

View File

@@ -0,0 +1,110 @@
/*
* fuzz_gltf_v3.cc — libFuzzer harness for tinygltf v3 parser.
*
* Fuzz targets:
* - Auto-detect (GLB or JSON) parse from arbitrary bytes
* - Exercises JSON parser, GLB header parsing, arena allocator,
* error stack, and all glTF entity parsing paths.
*
* Build (clang with libFuzzer):
* clang++ -g -O1 -fsanitize=fuzzer,address,undefined \
* -std=c++17 -fno-rtti -fno-exceptions \
* -I../../.. -o fuzz_gltf_v3 fuzz_gltf_v3.cc
*
* Run:
* ./fuzz_gltf_v3 corpus/ -max_len=65536
*
* Seed corpus: place valid .gltf and .glb files in corpus/
*/
#define TINYGLTF3_IMPLEMENTATION
#include "tiny_gltf_v3.h"
#include <cstdint>
#include <cstddef>
/* Memory budget to prevent OOM during fuzzing */
static const uint64_t FUZZ_MEMORY_BUDGET = 64ULL * 1024 * 1024; /* 64 MB */
static void fuzz_parse_auto(const uint8_t *data, size_t size) {
tg3_model model;
tg3_error_stack errors;
tg3_error_stack_init(&errors);
tg3_parse_options opts;
tg3_parse_options_init(&opts);
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
tg3_parse_auto(&model, &errors, data, (uint64_t)size,
"", 0, &opts);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
}
static void fuzz_parse_json(const uint8_t *data, size_t size) {
tg3_model model;
tg3_error_stack errors;
tg3_error_stack_init(&errors);
tg3_parse_options opts;
tg3_parse_options_init(&opts);
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
tg3_parse(&model, &errors, data, (uint64_t)size,
"", 0, &opts);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
}
static void fuzz_parse_glb(const uint8_t *data, size_t size) {
tg3_model model;
tg3_error_stack errors;
tg3_error_stack_init(&errors);
tg3_parse_options opts;
tg3_parse_options_init(&opts);
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
tg3_parse_glb(&model, &errors, data, (uint64_t)size,
"", 0, &opts);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
}
static void fuzz_parse_float32(const uint8_t *data, size_t size) {
tg3_model model;
tg3_error_stack errors;
tg3_error_stack_init(&errors);
tg3_parse_options opts;
tg3_parse_options_init(&opts);
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
opts.parse_float32 = 1;
tg3_parse_auto(&model, &errors, data, (uint64_t)size,
"", 0, &opts);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0) return 0;
/* Use first byte to select parse path, rest is the payload */
uint8_t selector = data[0] % 4;
const uint8_t *payload = data + 1;
size_t payload_size = size - 1;
switch (selector) {
case 0: fuzz_parse_auto(payload, payload_size); break;
case 1: fuzz_parse_json(payload, payload_size); break;
case 2: fuzz_parse_glb(payload, payload_size); break;
case 3: fuzz_parse_float32(payload, payload_size); break;
}
return 0;
}

File diff suppressed because it is too large Load Diff

4422
tiny_gltf_v3.h Normal file

File diff suppressed because it is too large Load Diff

2105
tinygltf_json.h Normal file

File diff suppressed because it is too large Load Diff