Compare commits

..

1 Commits

Author SHA1 Message Date
Syoyo Fujita
c6817d206b Remove some assert() 2023-06-07 19:10:44 +09:00
45 changed files with 865 additions and 20664 deletions

View File

@@ -1,45 +0,0 @@
## Description
What does this PR do? Provide a brief summary of the changes.
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactoring (no functional changes)
- [ ] Documentation update
- [ ] Other (please describe):
## Checklist
### Required for All PRs
- [ ] Reproducible glTF test file(s) are included (e.g., `models/regression/`, `tests/issue-***.gltf`, etc.)
- [ ] Unit tests are written and pass locally (`cd tests && ./tester`)
### Required for Feature PRs
- [ ] Specification document is included (e.g., `docs/spec/<feature-name>.md`)
- The spec should cover: purpose, API design, usage examples, and edge cases
### Security Policy
This project manages CVE assignments exclusively through GitHub Security Advisories.
PRs that include or reference independently obtained CVE IDs or external vulnerability disclosures will be closed.
## Test Instructions
How can reviewers verify your changes?
```
# Example:
cd build && cmake .. && make test
```
## Related Issues
Link related issues:
## Additional Notes
Any other context, screenshots, or information relevant to the review.

View File

@@ -14,7 +14,7 @@ jobs:
# steps:
# - name: Checkout
# uses: actions/checkout@v5
# uses: actions/checkout@v1
# - name: Build
# run: |
@@ -40,7 +40,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v2
- name: Build
run: |
@@ -60,18 +60,16 @@ jobs:
# https://help.github.com/en/actions/reference/software-installed-on-github-hosted-runners
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v1
- name: Configure
run: |
mkdir build
cd build
cmake --help
cmake -G "Visual Studio 17 2022" -A x64 -DTINYGLTF_BUILD_LOADER_EXAMPLE=On -DTINYGLTF_BUILD_GL_EXAMPLES=Off -DTINYGLTF_BUILD_VALIDATOR_EXAMPLE=On -DTINYGLTF_BUILD_TESTS=ON ..
cmake -G "Visual Studio 17 2022" -A x64 -DTINYGLTF_BUILD_LOADER_EXAMPLE=On -DTINYGLTF_BUILD_GL_EXAMPLES=Off -DTINYGLTF_BUILD_VALIDATOR_EXAMPLE=On ..
cd ..
- name: Build
run: cmake --build build --config Release
- name: Run tests
run: ctest --test-dir build -C Release --output-on-failure
build-linux:
@@ -80,7 +78,7 @@ jobs:
name: Buld with gcc
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v2
- name: build
run: |
g++ -std=c++11 -o loader_example loader_example.cc
@@ -102,23 +100,6 @@ jobs:
./tester_noexcept
cd ..
- name: v3_c_tests
run: |
cd tests
cc -I../ -std=c11 -g -O0 -DTINYGLTF3_ENABLE_FS \
-o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
./tester_v3_c
cc -I../ -std=c11 -g -O0 -DTINYGLTF3_ENABLE_FS \
-o tester_v3_c_v1port tester_v3_c_v1port.c ../tiny_gltf_v3.c
./tester_v3_c_v1port
cc -I../ -std=c11 -g -O0 \
-o tester_v3_json_c tester_v3_json_c.c
./tester_v3_json_c
cc -I../ -std=c11 -ffreestanding -g -O0 \
-o tester_v3_freestanding tester_v3_freestanding.c
./tester_v3_freestanding
cd ..
build-rapidjson-linux:
@@ -126,7 +107,7 @@ jobs:
name: Buld with gcc + rapidjson
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v2
- name: build
run: |
git clone https://github.com/Tencent/rapidjson
@@ -159,7 +140,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v2
- name: Build
run: |
sudo apt-get update
@@ -177,7 +158,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v1
- name: Build
run: |
clang++ -std=c++11 -g -O0 -o loader_example loader_example.cc
@@ -185,3 +166,4 @@ jobs:
git clone https://github.com/Tencent/rapidjson
clang++ -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -g -O0 -o loader_example loader_example.cc

View File

@@ -1,537 +0,0 @@
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@v5
- name: Configure
run: cmake -B build -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON -DTINYGLTF_BUILD_TESTS=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@v5
- 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 -DTINYGLTF_BUILD_TESTS=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@v5
- name: Configure
run: cmake -B build -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON -DTINYGLTF_BUILD_TESTS=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@v5
- name: Configure
run: cmake -B build -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON -DTINYGLTF_BUILD_TESTS=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@v5
- 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 -DTINYGLTF_BUILD_TESTS=ON ..
- 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@v5
- 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 -DTINYGLTF_BUILD_TESTS=ON ..
- 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@v5
- 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@v5
- 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 -DTINYGLTF_BUILD_TESTS=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
# 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@v5
- 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@v5
- 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@v5
- name: Build with CMake Header-Only
run: |
mkdir build
cmake -B build -DTINYGLTF_HEADER_ONLY=ON -DTINYGLTF_BUILD_LOADER_EXAMPLE=ON -DTINYGLTF_BUILD_TESTS=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
# v3 C tests through Meson on the primary desktop platforms.
v3-c-meson:
runs-on: ${{ matrix.os }}
name: v3 C Meson (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Install Meson
run: python -m pip install meson ninja
- name: Configure
run: meson setup build-meson -Dtests=true
- name: Build
run: meson compile -C build-meson
- name: Run v3 C tests
run: meson test -C build-meson --print-errorlogs
# Special Configuration: RapidJSON Backend
linux-rapidjson:
runs-on: ubuntu-latest
name: Linux x64 (GCC) - RapidJSON Backend
steps:
- name: Checkout
uses: actions/checkout@v5
- 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 -DTINYGLTF_BUILD_TESTS=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@v5
- 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@v5
- 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
# v3 C runtime: internal security regression tests + ported v1 unit tests.
v3-c-tests:
runs-on: ubuntu-latest
name: v3 C tests (Linux x64, GCC)
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Build tester_v3_c
run: |
cd tests
cc -I../ -std=c11 -g -O0 -Wall -Wextra -Wpedantic -Werror -DTINYGLTF3_ENABLE_FS \
-o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
- name: Build tester_v3_c_v1port
run: |
cd tests
cc -I../ -std=c11 -g -O0 -Wall -Wextra -Wpedantic -Werror -DTINYGLTF3_ENABLE_FS \
-o tester_v3_c_v1port tester_v3_c_v1port.c ../tiny_gltf_v3.c
- name: Run tester_v3_c (security regressions)
run: |
cd tests
./tester_v3_c
- name: Run tester_v3_c_v1port (18 ported unit tests)
run: |
cd tests
./tester_v3_c_v1port
# v3 C runtime under stock Ubuntu clang.
v3-c-tests-clang:
runs-on: ubuntu-latest
name: v3 C tests (Linux x64, Clang)
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Install clang
run: |
sudo apt-get update
sudo apt-get install -y clang
- name: Build tester_v3_c
run: |
cd tests
clang -I../ -std=c11 -g -O0 -Werror -Weverything \
-Wno-padded -Wno-unsafe-buffer-usage -Wno-switch-default \
-Wno-format-nonliteral -Wno-float-equal -Wno-cast-align \
-Wno-declaration-after-statement -Wno-unknown-warning-option \
-Wno-pre-c11-compat \
-DTINYGLTF3_ENABLE_FS \
-o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
- name: Build tester_v3_c_v1port
run: |
cd tests
clang -I../ -std=c11 -g -O0 -Werror -Weverything \
-Wno-padded -Wno-unsafe-buffer-usage -Wno-switch-default \
-Wno-format-nonliteral -Wno-float-equal -Wno-cast-align \
-Wno-declaration-after-statement -Wno-unknown-warning-option \
-Wno-pre-c11-compat \
-DTINYGLTF3_ENABLE_FS \
-o tester_v3_c_v1port tester_v3_c_v1port.c ../tiny_gltf_v3.c
- name: Run tester_v3_c
run: |
cd tests
./tester_v3_c
- name: Run tester_v3_c_v1port
run: |
cd tests
./tester_v3_c_v1port
# v3 C runtime under bleeding-edge clang 21 (matches local dev environment).
v3-c-tests-clang21:
runs-on: ubuntu-24.04
name: v3 C tests (Linux x64, Clang 21)
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Install clang 21
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21
- name: Build tester_v3_c
run: |
cd tests
clang-21 -I../ -std=c11 -g -O0 -Werror -Weverything \
-Wno-padded -Wno-unsafe-buffer-usage -Wno-switch-default \
-Wno-format-nonliteral -Wno-float-equal -Wno-cast-align \
-Wno-declaration-after-statement -Wno-unknown-warning-option \
-Wno-pre-c11-compat \
-DTINYGLTF3_ENABLE_FS \
-o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
- name: Build tester_v3_c_v1port
run: |
cd tests
clang-21 -I../ -std=c11 -g -O0 -Werror -Weverything \
-Wno-padded -Wno-unsafe-buffer-usage -Wno-switch-default \
-Wno-format-nonliteral -Wno-float-equal -Wno-cast-align \
-Wno-declaration-after-statement -Wno-unknown-warning-option \
-Wno-pre-c11-compat \
-DTINYGLTF3_ENABLE_FS \
-o tester_v3_c_v1port tester_v3_c_v1port.c ../tiny_gltf_v3.c
- name: Run tester_v3_c
run: |
cd tests
./tester_v3_c
- name: Run tester_v3_c_v1port
run: |
cd tests
./tester_v3_c_v1port
# v3 C runtime built with MSVC at warning-level 4 (/W4 /WX).
v3-c-tests-msvc:
runs-on: windows-latest
name: v3 C tests (Windows x64, MSVC /W4)
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Build and run tester_v3_c
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cd tests
cl /nologo /W4 /WX /std:c11 /Zi /Od /D_CRT_SECURE_NO_WARNINGS /DTINYGLTF3_ENABLE_FS /I.. tester_v3_c.c ..\tiny_gltf_v3.c /Fe:tester_v3_c.exe
tester_v3_c.exe
- name: Build and run tester_v3_c_v1port
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
cd tests
cl /nologo /W4 /WX /std:c11 /Zi /Od /D_CRT_SECURE_NO_WARNINGS /DTINYGLTF3_ENABLE_FS /I.. tester_v3_c_v1port.c ..\tiny_gltf_v3.c /Fe:tester_v3_c_v1port.exe
tester_v3_c_v1port.exe
# v3 C runtime under ASan + UBSan for memory-safety + UB checks.
v3-c-tests-sanitizers:
runs-on: ubuntu-latest
name: v3 C tests (Clang ASan + UBSan)
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Build tester_v3_c with ASan + UBSan
run: |
cd tests
clang -I../ -std=c11 -g -O1 -DTINYGLTF3_ENABLE_FS \
-fsanitize=address,undefined -fno-omit-frame-pointer \
-o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
- name: Build tester_v3_c_v1port with ASan + UBSan
run: |
cd tests
clang -I../ -std=c11 -g -O1 -DTINYGLTF3_ENABLE_FS \
-fsanitize=address,undefined -fno-omit-frame-pointer \
-o tester_v3_c_v1port tester_v3_c_v1port.c ../tiny_gltf_v3.c
- name: Run tester_v3_c
env:
ASAN_OPTIONS: detect_leaks=1:halt_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: |
cd tests
./tester_v3_c
- name: Run tester_v3_c_v1port
env:
ASAN_OPTIONS: detect_leaks=1:halt_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: |
cd tests
./tester_v3_c_v1port

72
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '21 20 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -1,53 +0,0 @@
name: MSYS2 MinGW-w64 Windows 64bit Build
on:
push:
branches:
- release
- devel
paths:
- 'tiny_gltf.*'
- 'tinygltf_json_c.h'
- 'CMakeLists.txt'
- 'meson.build'
- 'meson_options.txt'
- 'tests/tester_v3*.c'
- '.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@v5
- 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 \
-DTINYGLTF_BUILD_TESTS=ON
- name: Build
run: |
cmake --build build
- name: Run tests
run: |
ctest --test-dir build --output-on-failure

30
.gitignore vendored
View File

@@ -1,5 +1,4 @@
# CMake
/build/
CMakeCache.txt
CMakeFiles
CMakeScripts
@@ -22,13 +21,9 @@ premake5.tar.gz
*.vcxproj*
.vs
# default cmake build dir
build/
#binary directories
bin/
obj/
out/
#runtime gui config
imgui.ini
@@ -70,35 +65,10 @@ imgui.ini
*.app
loader_example
# Compiled test binaries (built by tests/Makefile, CMakeLists.txt, meson.build)
tests/tester
tests/tester_noexcept
tests/tester_customjson
tests/tester_intensive_customjson
tests/tester_v3
tests/tester_v3_c
tests/tester_v3_c_cpp
tests/tester_v3_c_v1port
tests/tester_v3_c_v1port_cpp
tests/tester_v3_freestanding
tests/tester_v3_json_c
tests/fuzzer/fuzz_gltf
tests/fuzzer/fuzz_gltf_customjson
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

10
.travis-before-install.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/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

63
.travis.yml Normal file
View File

@@ -0,0 +1,63 @@
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,100 +1,38 @@
cmake_minimum_required(VERSION 3.6)
project(tinygltf)
PROJECT (tinygltf)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED On)
set(CMAKE_CXX_EXTENSIONS Off)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
SET(CMAKE_CXX_STANDARD 11)
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()
function(add_tinygltf_v3_c_test target)
add_executable(${target} ${ARGN})
target_include_directories(${target} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
)
set_target_properties(${target} PROPERTIES
C_STANDARD 11
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
)
add_test(NAME ${target} COMMAND ${target} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
endfunction()
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)
# Intensive parser tests for the custom JSON backend
add_executable(tester_intensive_customjson tests/tester_intensive_customjson.cc)
target_include_directories(tester_intensive_customjson PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/tests
)
target_compile_definitions(tester_intensive_customjson PRIVATE TINYGLTF_USE_CUSTOM_JSON)
add_test(NAME tester_intensive_customjson COMMAND tester_intensive_customjson WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
add_tinygltf_v3_c_test(tester_v3_c tests/tester_v3_c.c tiny_gltf_v3.c)
target_compile_definitions(tester_v3_c PRIVATE TINYGLTF3_ENABLE_FS)
add_tinygltf_v3_c_test(tester_v3_c_v1port tests/tester_v3_c_v1port.c tiny_gltf_v3.c)
target_compile_definitions(tester_v3_c_v1port PRIVATE TINYGLTF3_ENABLE_FS)
add_tinygltf_v3_c_test(tester_v3_json_c tests/tester_v3_json_c.c)
add_tinygltf_v3_c_test(tester_v3_freestanding tests/tester_v3_freestanding.c)
if (CMAKE_C_COMPILER_ID MATCHES "Clang|GNU")
target_compile_options(tester_v3_freestanding PRIVATE -ffreestanding)
endif()
endif (TINYGLTF_BUILD_TESTS)
#
# for add_subdirectory and standalone build
#
@@ -118,40 +56,21 @@ 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/tinygltf)
install(EXPORT tinygltfTargets NAMESPACE tinygltf:: FILE TinyGLTFTargets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake)
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/tinygltf)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TinyGLTFConfig.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake)
# Do not install .lib even if !TINYGLTF_HEADER_ONLY
INSTALL ( FILES
json.hpp
stb_image.h
stb_image_write.h
tiny_gltf.h
tiny_gltf_v3.h
tiny_gltf_v3.c
tinygltf_json.h
tinygltf_json_c.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)

108
README.md
View File

@@ -1,79 +1,15 @@
# Header only C++ tiny glTF library(loader/saver).
`TinyGLTF` is a header only C++ glTF 2.0 https://github.com/KhronosGroup/glTF library.
`TinyGLTF` is a header only C++11 glTF 2.0 https://github.com/KhronosGroup/glTF library.
## TinyGLTF v3 (new major release)
**`tiny_gltf_v3.h`** is the new major version of TinyGLTF.
The new C implementation (`tiny_gltf_v3.c` + `tinygltf_json_c.h`) is currently **experimental**.
### What's new in v3
v3 is a ground-up rewrite with a C-centric, low-overhead design:
- **Pure C POD structs** — no STL containers in the public API; easy to bind to other languages.
- **Arena-based memory management** — all parse-time allocations come from a single arena; a single `tg3_model_free()` frees everything.
- **Structured error reporting** — `tg3_error_stack` provides machine-readable errors with severity levels and source locations.
- **Custom JSON backend** — backed by `tinygltf_json_c.h`, a locale-independent pure-C JSON parser/serializer used by the v3 runtime.
- **Streaming callbacks** — opt-in streaming parse/write via user-supplied callbacks.
- **No RTTI, no exceptions required** — suitable for embedded and game-engine use.
- **Opt-in filesystem and image I/O** — `TINYGLTF3_ENABLE_FS` / `TINYGLTF3_ENABLE_STB_IMAGE` are off by default; you control when and how assets are loaded.
- **C++20 coroutine facade** (optional, auto-detected). C17/C++17 default.
- **Hardened against untrusted input** — URI sanitization, post-parse index-bounds validation (default-on, opt-out via `tg3_parse_options.validate_indices = 0`), strict numeric range checks; exercised by a libFuzzer harness and by a cross-version verifier that compares parsed output against the v1 C++ reference loader. See the `Security Considerations` block at the top of `tiny_gltf_v3.h`.
### Quick start (v3)
Copy `tiny_gltf_v3.h`, `tiny_gltf_v3.c`, and `tinygltf_json_c.h` to your project.
Compile `tiny_gltf_v3.c` as C11 or newer. Define `TINYGLTF3_ENABLE_FS` when
building `tiny_gltf_v3.c` if you want `tg3_parse_file()` to use stdio-backed
filesystem helpers. The legacy `TINYGLTF3_IMPLEMENTATION` include path remains
available for compatibility.
```c
#include "tiny_gltf_v3.h"
```
Loading a glTF file:
```c
tg3_parse_options opts;
tg3_error_stack errors;
tg3_model model;
tg3_parse_options_init(&opts);
tg3_error_stack_init(&errors);
tg3_error_code err = tg3_parse_file(&model, &errors, "scene.gltf", 10, &opts);
if (err != TG3_OK) {
for (uint32_t i = 0; i < errors.count; i++) {
fprintf(stderr, "[%d] %s\n", (int)errors.entries[i].severity,
errors.entries[i].message ? errors.entries[i].message : "(null)");
}
}
// ... use model ...
tg3_model_free(&model);
tg3_error_stack_free(&errors);
```
### Testing & verification
The v3 C runtime ships with three layers of automated coverage:
- **`tests/tester_v3_c.c`** — internal unit checks plus security regression tests (path traversal, negative `byteStride`, OOB indices, error-message lifetime, …). Build via `make` in `tests/`; run `./tester_v3_c` for the internal suite or `./tester_v3_c <file.gltf|file.glb>` to parse a single asset.
- **`test_runner.py`** — a cross-version verifier that runs the v1 C++ reference loader (`loader_example`) and the v3 C tester against every model in `glTF-Sample-Models/2.0`, then diffs a structured DIGEST block (buffer FNV64 hashes, accessor/bufferView fields, primitive attribute maps, node TRS, material PBR factors, skin/animation/scene topology, …). v1 is the ground truth.
- **`tests/v3/fuzzer/`** — libFuzzer harness with ASan + UBSan (`make run` builds and runs `fuzz_gltf_v3_c`). Crafted regression inputs live in `tests/v3/security/` and are seeded into `tests/v3/fuzzer/corpus/`.
`TinyGLTF` uses Niels Lohmann's json library (https://github.com/nlohmann/json), so now it requires C++11 compiler.
(Also, you can use RadpidJSON as an JSON backend)
If you are looking for old, C++03 version, please use `devel-picojson` branch (but not maintained anymore).
## Status
> ⚠️ **v2 deprecation notice:** `tiny_gltf.h` (v2) remains fully functional and is still supported,
> but it is now in **maintenance mode only** — no new features will be added.
> v2 will be **sunset after mid-2026**. `tiny_gltf_v3.h` is the intended successor, but the new C v3 runtime is still **experimental**.
Currently TinyGLTF is stable and maintenance mode. No drastic changes and feature additions planned.
TinyGLTF v3's C runtime (`tiny_gltf_v3.h` + `tiny_gltf_v3.c`) is available for evaluation and early adoption,
but its API/behavior may still change while the implementation matures.
Currently TinyGLTF v2 is stable and in 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).
@@ -90,6 +26,10 @@ Currently TinyGLTF v2 is stable and in maintenance mode. No drastic changes and
## 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
@@ -154,6 +94,22 @@ Users who want to run TinyGLTF securely and safely(e.g. need to handle malcious
I recommend to build TinyGLTF for WASM target.
WASI build example is located in [wasm](wasm) .
## Projects using TinyGLTF
* px_render Single header C++ Libraries for Thread Scheduling, Rendering, and so on... https://github.com/pplux/px
* Physical based rendering with Vulkan using glTF 2.0 models https://github.com/SaschaWillems/Vulkan-glTF-PBR
* GLTF loader plugin for OGRE 2.1. Support for PBR materials via HLMS/PBS https://github.com/Ybalrid/Ogre_glTF
* [TinyGltfImporter](http://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1TinyGltfImporter.html) plugin for [Magnum](https://github.com/mosra/magnum), a lightweight and modular C++11/C++14 graphics middleware for games and data visualization.
* [Diligent Engine](https://github.com/DiligentGraphics/DiligentEngine) - A modern cross-platform low-level graphics library and rendering framework
* Lighthouse 2: a rendering framework for real-time ray tracing / path tracing experiments. https://github.com/jbikker/lighthouse2
* [QuickLook GLTF](https://github.com/toshiks/glTF-quicklook) - quicklook plugin for macos. Also SceneKit wrapper for tinygltf.
* [GlslViewer](https://github.com/patriciogonzalezvivo/glslViewer) - live GLSL coding for MacOS and Linux
* [Vulkan-Samples](https://github.com/KhronosGroup/Vulkan-Samples) - The Vulkan Samples is collection of resources to help you develop optimized Vulkan applications.
* [TDME2](https://github.com/andreasdr/tdme2) - TDME2 - ThreeDeeMiniEngine2 is a lightweight 3D engine including tools suited for 3D game development using C++11
* [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.
* Your projects here! (Please send PR)
## TODOs
@@ -204,10 +160,9 @@ Model model;
TinyGLTF loader;
std::string err;
std::string warn;
std::string filename = "input.gltf";
bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, filename);
//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, filename); // for binary glTF(.glb)
bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, argv[1]);
//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, argv[1]); // for binary glTF(.glb)
if (!warn.empty()) {
printf("Warn: %s\n", warn.c_str());
@@ -218,7 +173,8 @@ if (!err.empty()) {
}
if (!ret) {
printf("Failed to parse glTF: %s\n", filename.c_str());
printf("Failed to parse glTF\n");
return -1;
}
```
@@ -239,6 +195,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
@@ -255,11 +212,6 @@ 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

18
appveyor.yml Normal file
View File

@@ -0,0 +1,18 @@
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

View File

@@ -1,70 +0,0 @@
# 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 ../tiny_gltf_v3.c ../tinygltf_json_c.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

View File

@@ -1,414 +0,0 @@
/*
* 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,
int skip_extras_values = 0,
int borrow_input_buffers = 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;
opts.skip_extras_values = skip_extras_values;
opts.borrow_input_buffers = borrow_input_buffers;
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"
" --skip-extras-values\n"
" Skip materializing extras/unknown extension values\n"
" --borrow-input-buffers\n"
" Let GLB buffers reference caller-owned input bytes\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;
int skip_extras_values = 0;
int borrow_input_buffers = 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], "--skip-extras-values") == 0) {
skip_extras_values = 1;
} else if (strcmp(argv[i], "--borrow-input-buffers") == 0) {
borrow_input_buffers = 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" :
skip_extras_values ? ", skip extras" :
borrow_input_buffers ? ", borrow buffers" : "");
}
BenchResult r = bench_file(file.c_str(), iterations, warmup, quiet,
float32_mode, skip_extras_values,
borrow_input_buffers);
if (csv) {
print_csv_row(r);
} else {
print_result(r);
printf("\n");
}
}
return 0;
}

View File

@@ -1,740 +0,0 @@
/*
* 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

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

View File

@@ -6,10 +6,7 @@
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "tiny_gltf.h"
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <iostream>
@@ -855,206 +852,6 @@ static void Dump(const tinygltf::Model &model) {
}
}
/* ===== Digest helpers (used to compare v1 vs v3 parses) ===================== */
static uint64_t fnv64(const unsigned char *data, size_t n) {
uint64_t h = 0xcbf29ce484222325ULL;
for (size_t i = 0; i < n; ++i) { h ^= data[i]; h *= 0x100000001b3ULL; }
return h;
}
static void d_str(const std::string &s) {
putchar('"');
for (unsigned char c : s) {
if (c == '"' || c == '\\') { putchar('\\'); putchar((char)c); }
else if (c < 0x20 || c >= 0x7f) putchar('?');
else putchar((char)c);
}
putchar('"');
}
static void d_dbl(double v) { printf("%.7g", v); }
static void d_dbl_arr(const double *v, size_t n) {
putchar('[');
for (size_t i = 0; i < n; ++i) { if (i) putchar(','); d_dbl(v[i]); }
putchar(']');
}
static void d_dbl_vec(const std::vector<double> &v) {
d_dbl_arr(v.data(), v.size());
}
static void PrintDigest(const tinygltf::Model &m) {
printf("DIGEST_BEGIN\n");
printf("asset version=");
d_str(m.asset.version);
printf(" generator=");
d_str(m.asset.generator);
printf("\n");
for (size_t i = 0; i < m.buffers.size(); ++i) {
const auto &b = m.buffers[i];
uint64_t h = b.data.empty() ? 0 : fnv64(b.data.data(), b.data.size());
printf("buffer %zu byte_length=%llu fnv64=0x%016llx\n",
i, (unsigned long long)b.data.size(), (unsigned long long)h);
}
for (size_t i = 0; i < m.bufferViews.size(); ++i) {
const auto &bv = m.bufferViews[i];
printf("buffer_view %zu buffer=%d byte_offset=%llu byte_length=%llu byte_stride=%u\n",
i, bv.buffer, (unsigned long long)bv.byteOffset,
(unsigned long long)bv.byteLength, (unsigned)bv.byteStride);
}
for (size_t i = 0; i < m.accessors.size(); ++i) {
const auto &a = m.accessors[i];
printf("accessor %zu buffer_view=%d byte_offset=%llu component_type=%d count=%llu type=%d normalized=%d min=",
i, a.bufferView, (unsigned long long)a.byteOffset, a.componentType,
(unsigned long long)a.count, a.type, a.normalized ? 1 : 0);
d_dbl_vec(a.minValues);
printf(" max=");
d_dbl_vec(a.maxValues);
printf(" sparse=%d\n", a.sparse.isSparse ? 1 : 0);
}
for (size_t i = 0; i < m.meshes.size(); ++i) {
const auto &me = m.meshes[i];
printf("mesh %zu primitives_count=%zu weights_count=%zu\n",
i, me.primitives.size(), me.weights.size());
for (size_t j = 0; j < me.primitives.size(); ++j) {
const auto &p = me.primitives[j];
printf("prim %zu %zu indices=%d material=%d mode=%d attrs=[",
i, j, p.indices, p.material, p.mode);
// attributes is std::map → already sorted by key
bool first = true;
for (const auto &kv : p.attributes) {
if (!first) putchar(',');
printf("%s:%d", kv.first.c_str(), kv.second);
first = false;
}
printf("] targets_count=%zu\n", p.targets.size());
}
}
for (size_t i = 0; i < m.nodes.size(); ++i) {
const auto &n = m.nodes[i];
double t[3] = {0, 0, 0};
double r[4] = {0, 0, 0, 1};
double s[3] = {1, 1, 1};
double mat[16] = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
int has_matrix = (n.matrix.size() == 16) ? 1 : 0;
if (n.translation.size() == 3) std::copy(n.translation.begin(), n.translation.end(), t);
if (n.rotation.size() == 4) std::copy(n.rotation.begin(), n.rotation.end(), r);
if (n.scale.size() == 3) std::copy(n.scale.begin(), n.scale.end(), s);
if (has_matrix) std::copy(n.matrix.begin(), n.matrix.end(), mat);
printf("node %zu mesh=%d skin=%d camera=%d light=%d children_count=%zu has_matrix=%d t=",
i, n.mesh, n.skin, n.camera, n.light, n.children.size(), has_matrix);
d_dbl_arr(t, 3);
printf(" r=");
d_dbl_arr(r, 4);
printf(" s=");
d_dbl_arr(s, 3);
printf(" matrix=");
d_dbl_arr(mat, 16);
printf(" weights_count=%zu\n", n.weights.size());
}
for (size_t i = 0; i < m.materials.size(); ++i) {
const auto &mat = m.materials[i];
double ef[3] = {0, 0, 0};
double bcf[4] = {1, 1, 1, 1};
if (mat.emissiveFactor.size() == 3)
std::copy(mat.emissiveFactor.begin(), mat.emissiveFactor.end(), ef);
if (mat.pbrMetallicRoughness.baseColorFactor.size() == 4)
std::copy(mat.pbrMetallicRoughness.baseColorFactor.begin(),
mat.pbrMetallicRoughness.baseColorFactor.end(), bcf);
printf("material %zu alpha_mode=", i);
d_str(mat.alphaMode);
printf(" alpha_cutoff=");
d_dbl(mat.alphaCutoff);
printf(" double_sided=%d emissive=", mat.doubleSided ? 1 : 0);
d_dbl_arr(ef, 3);
printf(" base_color_factor=");
d_dbl_arr(bcf, 4);
printf(" metallic=");
d_dbl(mat.pbrMetallicRoughness.metallicFactor);
printf(" roughness=");
d_dbl(mat.pbrMetallicRoughness.roughnessFactor);
printf(" base_color_tex=%d normal_tex=%d occlusion_tex=%d emissive_tex=%d\n",
mat.pbrMetallicRoughness.baseColorTexture.index,
mat.normalTexture.index,
mat.occlusionTexture.index,
mat.emissiveTexture.index);
}
for (size_t i = 0; i < m.textures.size(); ++i) {
const auto &t = m.textures[i];
printf("texture %zu source=%d sampler=%d\n", i, t.source, t.sampler);
}
for (size_t i = 0; i < m.samplers.size(); ++i) {
const auto &s = m.samplers[i];
printf("sampler %zu min_filter=%d mag_filter=%d wrap_s=%d wrap_t=%d\n",
i, s.minFilter, s.magFilter, s.wrapS, s.wrapT);
}
for (size_t i = 0; i < m.images.size(); ++i) {
const auto &im = m.images[i];
/* mime_type and uri normalization differ between v1/v3 (data URIs,
extension inference); buffer_view reference is the parse-fidelity bit. */
printf("image %zu buffer_view=%d\n", i, im.bufferView);
}
for (size_t i = 0; i < m.skins.size(); ++i) {
const auto &s = m.skins[i];
printf("skin %zu inverse_bind_matrices=%d skeleton=%d joints_count=%zu\n",
i, s.inverseBindMatrices, s.skeleton, s.joints.size());
}
for (size_t i = 0; i < m.animations.size(); ++i) {
const auto &a = m.animations[i];
printf("animation %zu channels_count=%zu samplers_count=%zu\n",
i, a.channels.size(), a.samplers.size());
for (size_t j = 0; j < a.channels.size(); ++j) {
const auto &c = a.channels[j];
printf("chan %zu %zu sampler=%d target_node=%d target_path=", i, j,
c.sampler, c.target_node);
d_str(c.target_path);
printf("\n");
}
for (size_t j = 0; j < a.samplers.size(); ++j) {
const auto &as = a.samplers[j];
printf("samp %zu %zu input=%d output=%d interpolation=", i, j,
as.input, as.output);
d_str(as.interpolation);
printf("\n");
}
}
for (size_t i = 0; i < m.cameras.size(); ++i) {
const auto &c = m.cameras[i];
bool is_persp = (c.type == "perspective");
printf("camera %zu type=", i);
d_str(c.type);
if (is_persp) {
printf(" yfov=");
d_dbl(c.perspective.yfov);
printf(" znear=");
d_dbl(c.perspective.znear);
printf(" zfar=");
d_dbl(c.perspective.zfar);
printf(" aspect=");
d_dbl(c.perspective.aspectRatio);
} else {
printf(" xmag=");
d_dbl(c.orthographic.xmag);
printf(" ymag=");
d_dbl(c.orthographic.ymag);
printf(" znear=");
d_dbl(c.orthographic.znear);
printf(" zfar=");
d_dbl(c.orthographic.zfar);
}
printf("\n");
}
for (size_t i = 0; i < m.scenes.size(); ++i) {
const auto &s = m.scenes[i];
printf("scene %zu nodes_count=%zu\n", i, s.nodes.size());
}
printf("DIGEST_END\n");
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("Needs input.gltf\n");
@@ -1103,20 +900,6 @@ int main(int argc, char **argv) {
return -1;
}
printf("COUNTS"
" accessors=%zu animations=%zu buffers=%zu bufferViews=%zu"
" cameras=%zu images=%zu materials=%zu meshes=%zu nodes=%zu"
" samplers=%zu scenes=%zu skins=%zu textures=%zu lights=%zu\n",
model.accessors.size(), model.animations.size(),
model.buffers.size(), model.bufferViews.size(),
model.cameras.size(), model.images.size(),
model.materials.size(), model.meshes.size(),
model.nodes.size(), model.samplers.size(),
model.scenes.size(), model.skins.size(),
model.textures.size(), model.lights.size());
PrintDigest(model);
Dump(model);
return 0;

View File

@@ -1,52 +0,0 @@
project(
'tinygltf',
'c',
default_options: ['c_std=c11'],
meson_version: '>=0.55.0',
)
tinygltf_inc = include_directories('.', 'tests')
if get_option('tests')
tests_workdir = join_paths(meson.current_source_dir(), 'tests')
cc = meson.get_compiler('c')
tester_v3_c = executable(
'tester_v3_c',
['tests/tester_v3_c.c', 'tiny_gltf_v3.c'],
include_directories: tinygltf_inc,
c_args: ['-DTINYGLTF3_ENABLE_FS'],
install: false,
)
test('tester_v3_c', tester_v3_c, workdir: tests_workdir)
tester_v3_c_v1port = executable(
'tester_v3_c_v1port',
['tests/tester_v3_c_v1port.c', 'tiny_gltf_v3.c'],
include_directories: tinygltf_inc,
c_args: ['-DTINYGLTF3_ENABLE_FS'],
install: false,
)
test('tester_v3_c_v1port', tester_v3_c_v1port, workdir: tests_workdir)
tester_v3_json_c = executable(
'tester_v3_json_c',
'tests/tester_v3_json_c.c',
include_directories: tinygltf_inc,
install: false,
)
test('tester_v3_json_c', tester_v3_json_c, workdir: tests_workdir)
freestanding_args = []
if cc.get_id() in ['clang', 'gcc']
freestanding_args += ['-ffreestanding']
endif
tester_v3_freestanding = executable(
'tester_v3_freestanding',
'tests/tester_v3_freestanding.c',
include_directories: tinygltf_inc,
c_args: freestanding_args,
install: false,
)
test('tester_v3_freestanding', tester_v3_freestanding, workdir: tests_workdir)
endif

View File

@@ -1 +0,0 @@
option('tests', type: 'boolean', value: true, description: 'Build and run tinygltf tests')

View File

@@ -773,7 +773,7 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f
#ifdef __STDC_LIB_EXT1__
len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#else
len = snprintf(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x);
#endif
s->func(s->context, buffer, len);

View File

@@ -2,167 +2,63 @@
import glob
import os
import re
import subprocess
import sys
## Cross-version verifier: parses each sample model with the mature v1
## (loader_example) and the new v3 C tester, then compares both the COUNTS
## summary line and the structured DIGEST block both binaries emit.
## v1 is the ground truth.
## Simple test runner.
# -- config -----------------------
sample_model_dir = "/mnt/nfs/syoyo/glTF-Sample-Models"
# Absolute path pointing to your cloned git repo of https://github.com/KhronosGroup/glTF-Sample-Models
sample_model_dir = "/home/syoyo/work/glTF-Sample-Models"
base_model_dir = os.path.join(sample_model_dir, "2.0")
v1_bin = "./loader_example"
v3_bin = "./tests/tester_v3_c"
kinds = ["glTF", "glTF-Binary", "glTF-Embedded", "glTF-MaterialsCommon"]
# Include `glTF-Draco` when you build `loader_example` with draco support.
kinds = [ "glTF", "glTF-Binary", "glTF-Embedded", "glTF-MaterialsCommon"]
# ---------------------------------
COUNTS_RE = re.compile(r"^COUNTS\s+(.*)$", re.MULTILINE)
DIGEST_RE = re.compile(r"^DIGEST_BEGIN\n(.*?)^DIGEST_END$", re.MULTILINE | re.DOTALL)
failed = []
success = []
def run(filename):
def parse_counts(output):
m = COUNTS_RE.search(output)
if not m:
return None
counts = {}
for tok in m.group(1).split():
if "=" not in tok:
continue
k, v = tok.split("=", 1)
counts[k] = int(v)
return counts
def parse_digest(output):
m = DIGEST_RE.search(output)
if not m:
return None
return [line for line in m.group(1).splitlines() if line]
def run_binary(binary, filename):
p = subprocess.Popen(
[binary, filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
out, err = p.communicate()
return p.returncode, out.decode("utf-8", "replace"), err.decode("utf-8", "replace")
def diff_digests(v1, v3, max_lines=20):
"""Return a short summary of differences between two digest line lists."""
diffs = []
n = max(len(v1), len(v3))
for i in range(n):
a = v1[i] if i < len(v1) else "<missing>"
b = v3[i] if i < len(v3) else "<missing>"
if a != b:
diffs.append(" v1[{0}]: {1}".format(i, a))
diffs.append(" v3[{0}]: {1}".format(i, b))
if len(diffs) >= max_lines * 2:
diffs.append(" ... (truncated)")
break
return diffs
parse_failed = [] # v3 returned non-zero or no COUNTS/DIGEST
v1_skipped = [] # v1 returned non-zero or no COUNTS/DIGEST
counts_diff = [] # counts disagree
digest_diff = [] # digest disagrees
ok = []
def verify(filename):
print("Testing: " + filename)
cmd = ["./loader_example", filename]
try:
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = p.communicate()
except:
print("Failed to execute: ", cmd)
raise
rc1, out1, err1 = run_binary(v1_bin, filename)
c1 = parse_counts(out1) if rc1 == 0 else None
d1 = parse_digest(out1) if rc1 == 0 else None
if c1 is None or d1 is None:
v1_skipped.append(filename)
print(" v1 ground truth unavailable (rc={0}); skipping".format(rc1))
return
rc3, out3, err3 = run_binary(v3_bin, filename)
c3 = parse_counts(out3) if rc3 == 0 else None
d3 = parse_digest(out3) if rc3 == 0 else None
if c3 is None or d3 is None:
parse_failed.append((filename, rc3, err3.strip()))
print(" v3 FAILED (rc={0}): {1}".format(rc3, err3.strip()[:200]))
return
cdiffs = []
for k in sorted(set(c1) | set(c3)):
if c1.get(k) != c3.get(k):
cdiffs.append((k, c1.get(k), c3.get(k)))
if cdiffs:
counts_diff.append((filename, cdiffs))
print(" COUNTS MISMATCH:")
for k, a, b in cdiffs:
print(" {0}: v1={1} v3={2}".format(k, a, b))
return
if d1 != d3:
diffs = diff_digests(d1, d3)
digest_diff.append((filename, diffs))
print(" DIGEST MISMATCH ({0} v1 lines, {1} v3 lines):".format(len(d1), len(d3)))
for line in diffs[:8]:
print(line)
return
ok.append(filename)
if p.returncode != 0:
failed.append(filename)
print(stdout)
print(stderr)
else:
success.append(filename)
def test():
for d in sorted(os.listdir(base_model_dir)):
for d in os.listdir(base_model_dir):
p = os.path.join(base_model_dir, d)
if not os.path.isdir(p):
continue
for k in kinds:
targetDir = os.path.join(p, k)
g = sorted(
glob.glob(targetDir + "/*.gltf")
+ glob.glob(targetDir + "/*.glb")
)
for gltf in g:
verify(gltf)
if os.path.isdir(p):
for k in kinds:
targetDir = os.path.join(p, k)
g = glob.glob(targetDir + "/*.gltf") + glob.glob(targetDir + "/*.glb")
for gltf in g:
run(gltf)
def main():
if not os.path.exists(v1_bin):
sys.exit("error: v1 binary not found at {0}".format(v1_bin))
if not os.path.exists(v3_bin):
sys.exit("error: v3 binary not found at {0}".format(v3_bin))
test()
print("")
print("=== Summary ===")
print("OK : {0}".format(len(ok)))
print("Counts diff : {0}".format(len(counts_diff)))
print("Digest diff : {0}".format(len(digest_diff)))
print("v3 failed : {0}".format(len(parse_failed)))
print("v1 skipped : {0}".format(len(v1_skipped)))
print("Success : {0}".format(len(success)))
print("Failed : {0}".format(len(failed)))
for f, diffs in counts_diff:
print("COUNTS DIFF: " + f)
for k, a, b in diffs:
print(" {0}: v1={1} v3={2}".format(k, a, b))
for f, diffs in digest_diff:
print("DIGEST DIFF: " + f)
for line in diffs:
print(line)
for f, rc, err in parse_failed:
print("V3 FAIL: {0} (rc={1}) {2}".format(f, rc, err[:200]))
for fail in failed:
print("FAIL: " + fail)
if counts_diff or digest_diff or parse_failed:
sys.exit(1)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View File

@@ -1,19 +1,6 @@
# Use this for strict compilation check(will work on clang 3.8+)
#EXTRA_CXXFLAGS := -fsanitize=address -Wall -Werror -Weverything -Wno-c++11-long-long -DTINYGLTF_APPLY_CLANG_WEVERYTHING
all: ../tiny_gltf.h tester_v3_c tester_v3_c_v1port tester_v3_json_c tester_v3_freestanding
all: ../tiny_gltf.h
clang++ -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester tester.cc
clang++ -DTINYGLTF_NOEXCEPTION -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_noexcept tester.cc
clang++ -DTINYGLTF_USE_CUSTOM_JSON -I../ $(EXTRA_CXXFLAGS) -std=c++11 -g -O0 -o tester_intensive_customjson tester_intensive_customjson.cc
tester_v3_c: tester_v3_c.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h
clang -I../ -std=c11 -g -O0 -DTINYGLTF3_ENABLE_FS -o tester_v3_c tester_v3_c.c ../tiny_gltf_v3.c
tester_v3_c_v1port: tester_v3_c_v1port.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h
clang -I../ -std=c11 -g -O0 -DTINYGLTF3_ENABLE_FS -o tester_v3_c_v1port tester_v3_c_v1port.c ../tiny_gltf_v3.c
tester_v3_json_c: tester_v3_json_c.c ../tinygltf_json_c.h
clang -I../ -std=c11 -g -O0 -o tester_v3_json_c tester_v3_json_c.c
tester_v3_freestanding: tester_v3_freestanding.c ../tiny_gltf_v3.h ../tiny_gltf_v3.c ../tinygltf_json_c.h
clang -I../ -std=c11 -ffreestanding -g -O0 -o tester_v3_freestanding tester_v3_freestanding.c

View File

@@ -4,14 +4,9 @@ Do fuzzing test for TinyGLTF API.
## Supported API
* [x] LoadASCIIFromString
* [x] LoadASCIIFromMemory
* [ ] LoadBinaryFromMemory
### Custom JSON backend (`tinygltf_json.h`)
* [x] LoadASCIIFromString
* [x] LoadBinaryFromMemory
## Requirements
* meson
@@ -41,17 +36,11 @@ $ cd build
$ ninja
```
This builds two fuzzers:
* `fuzz_gltf` default nlohmann/json backend
* `fuzz_gltf_customjson` custom `tinygltf_json.h` backend (tests both ASCII and binary parsing paths)
## How to run
Increase memory limit. e.g. `-rss_limit_mb=50000`
```
$ ./fuzz_gltf -rss_limit_mb=20000 -jobs 4
$ ./fuzz_gltf_customjson -rss_limit_mb=20000 -jobs 4
```

View File

@@ -1,76 +0,0 @@
/*
* LLVM libFuzzer harness for tinygltf with the custom JSON backend
* (tinygltf_json.h).
*
* Exercises:
* 1. LoadASCIIFromString glTF JSON parsing
* 2. LoadBinaryFromMemory GLB binary parsing
*
* Build (clang with libFuzzer):
* clang++ -std=c++11 -fsanitize=address,fuzzer \
* -DTINYGLTF_USE_CUSTOM_JSON \
* -I../../ fuzz_gltf_customjson.cc \
* -o fuzz_gltf_customjson
*
* Run:
* ./fuzz_gltf_customjson -rss_limit_mb=20000 -jobs 4
*/
#include <cstdint>
#include <cstring>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_IMPLEMENTATION
#ifndef TINYGLTF_USE_CUSTOM_JSON
#define TINYGLTF_USE_CUSTOM_JSON
#endif
#include "tiny_gltf.h"
/* Fuzz the ASCII (JSON) parser path */
static void fuzz_ascii(const uint8_t *data, size_t size) {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
const char *str = reinterpret_cast<const char *>(data);
bool ret =
ctx.LoadASCIIFromString(&model, &err, &warn, str,
static_cast<unsigned int>(size), /* base_dir */ "");
(void)ret;
}
/* Fuzz the binary (GLB) parser path */
static void fuzz_binary(const uint8_t *data, size_t size) {
tinygltf::Model model;
tinygltf::TinyGLTF ctx;
std::string err;
std::string warn;
bool ret = ctx.LoadBinaryFromMemory(&model, &err, &warn, data,
static_cast<unsigned int>(size),
/* base_dir */ "");
(void)ret;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0) return 0;
/* Use the lowest bit of the first byte to select the parse path.
* The remaining bits are left for the fuzzer engine to explore;
* additional paths (e.g. LoadASCIIFromFile, check_sections flags)
* can be added here in the future using more selector bits. */
uint8_t selector = data[0];
const uint8_t *payload = data + 1;
size_t payload_size = size - 1;
if (selector & 1) {
fuzz_binary(payload, payload_size);
} else {
fuzz_ascii(payload, payload_size);
}
return 0;
}

View File

@@ -7,9 +7,3 @@ executable('fuzz_gltf',
cpp_args : '-fsanitize=address,fuzzer',
link_args : '-fsanitize=address,fuzzer' )
executable('fuzz_gltf_customjson',
'fuzz_gltf_customjson.cc',
include_directories : incdirs,
cpp_args : ['-fsanitize=address,fuzzer', '-DTINYGLTF_USE_CUSTOM_JSON'],
link_args : '-fsanitize=address,fuzzer' )

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());
REQUIRE(!warn.empty()); // relative image path won't exist in tests/
REQUIRE(saved.images.size() == model.images.size());
// The image uri in CubeImageUriMultipleSpaces.gltf is not encoded and
@@ -494,23 +494,25 @@ TEST_CASE("image-uri-spaces", "[issue-236]") {
}
TEST_CASE("serialize-empty-material", "[issue-294]") {
tinygltf::Model m;
// Add default constructed material to model
m.materials.push_back({});
// Serialize model to output stream
tinygltf::Material mat;
mat.pbrMetallicRoughness.baseColorFactor = {1.0f, 1.0f, 1.0f, 1.0f}; // default baseColorFactor
m.materials.push_back(mat);
std::stringstream os;
tinygltf::TinyGLTF ctx;
bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false);
REQUIRE(true == ret);
// Parse serialized model
// use nlohmann json
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());
CHECK(j["materials"][0].is_object());
CHECK(j["materials"][0].empty());
REQUIRE(j["materials"][0].is_object());
}
TEST_CASE("empty-skeleton-id", "[issue-321]") {
@@ -662,11 +664,10 @@ 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::FsCallbacks* fs, const tinygltf::URICallbacks *uri_cb,
std::string *out_uri, void *user_pointer) -> bool {
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);
@@ -700,13 +701,12 @@ 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::FsCallbacks* fs, const tinygltf::URICallbacks *uri_cb,
std::string *out_uri, void *user_pointer) -> bool {
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;
@@ -757,529 +757,3 @@ TEST_CASE("load-issue-416-model", "[issue-416]") {
// 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);
}
#ifdef TINYGLTF_USE_CUSTOM_JSON
/* Regression test: in float32_mode, integer-only tokens with more than 9
* digits must still be parsed as integers (is_int == 1), not floats.
* Previously, max_sig=9 was applied to the integer part too, causing excess
* digits to bump exp10, which broke the exp10==0 guard in the integer
* fast-path and mis-classified the value as a float. */
TEST_CASE("cj-float32-long-integer", "[customjson]") {
// Values chosen to cover exactly-at, just-over, and near int64 boundaries.
struct {
const char *text;
int64_t expected;
} cases[] = {
{ "1234567890", 1234567890LL }, /* 10 digits */
{ "12345678901", 12345678901LL }, /* 11 digits */
{ "1000000000000", 1000000000000LL }, /* 13 digits */
{ "9223372036854775807", INT64_MAX }, /* max int64 (19 digits) */
{ "-1234567890", -1234567890LL }, /* negative 10 digits */
{ "-9223372036854775808", INT64_MIN }, /* min int64 */
};
for (auto &tc : cases) {
int is_int = 0;
int64_t ival = 0;
double dval = 0.0;
const char *end = tc.text + strlen(tc.text);
const char *ret = cj_parse_number(tc.text, end, &is_int, &ival, &dval, /*float32_mode=*/1);
CAPTURE(tc.text);
REQUIRE(ret != nullptr);
CHECK(is_int == 1);
CHECK(ival == tc.expected);
}
}
#endif /* TINYGLTF_USE_CUSTOM_JSON */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,559 +0,0 @@
/*
* tester_v3_c_v1port.c — Parse/load tests ported from tester.cc (v1) to the
* pure-C v3 runtime. Each PORT_CASE corresponds to a TEST_CASE in tester.cc;
* the comment marks the original test name.
*
* Skipped from tester.cc (require v3 writer or v1-internal helpers, out of
* scope for this port):
* serialize-empty-material, serialize-empty-node, serialize-light-index,
* serialize-empty-scene, serialize-node-emitter, serialize-lods,
* serialize-const-image, serialize-image-callback, serialize-image-failure,
* write-image-issue, empty-images-not-written, empty-bin-buffer,
* parse-integer, parse-unsigned, parse-integer-array,
* expandpath-utf-8, cj-float32-long-integer
*
* Build (run from tests/):
* make tester_v3_c_v1port
* Run:
* ./tester_v3_c_v1port
*/
#include "tiny_gltf_v3.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* --- thin helpers ---------------------------------------------------------- */
static int tg3_str_eq(const tg3_str *s, const char *cstr) {
size_t n = strlen(cstr);
return s->data && s->len == (uint32_t)n &&
memcmp(s->data, cstr, n) == 0;
}
static int tg3_str_contains(const tg3_str *s, char ch) {
uint32_t i;
if (!s->data) return 0;
for (i = 0; i < s->len; ++i) {
if (s->data[i] == ch) return 1;
}
return 0;
}
static int has_extension(const tg3_extras_ext *e, const char *name) {
uint32_t i;
if (!e || !e->extensions) return 0;
for (i = 0; i < e->extensions_count; ++i) {
if (tg3_str_eq(&e->extensions[i].name, name)) return 1;
}
return 0;
}
static const tg3_extension *find_extension(const tg3_extras_ext *e, const char *name) {
uint32_t i;
if (!e || !e->extensions) return NULL;
for (i = 0; i < e->extensions_count; ++i) {
if (tg3_str_eq(&e->extensions[i].name, name)) return &e->extensions[i];
}
return NULL;
}
static const tg3_value *value_get(const tg3_value *v, const char *key) {
uint32_t i;
if (!v || v->type != TG3_VALUE_OBJECT || !v->object_data) return NULL;
for (i = 0; i < v->object_count; ++i) {
if (tg3_str_eq(&v->object_data[i].key, key)) {
return &v->object_data[i].value;
}
}
return NULL;
}
/* Read a glTF/GLB file into a heap buffer; returns 1 on success. */
static int slurp(const char *path, uint8_t **out, size_t *out_len) {
FILE *fp = fopen(path, "rb");
long sz;
size_t got;
uint8_t *buf;
if (!fp) return 0;
if (fseek(fp, 0, SEEK_END) != 0 || (sz = ftell(fp)) < 0 ||
fseek(fp, 0, SEEK_SET) != 0) { fclose(fp); return 0; }
buf = (uint8_t *)malloc((size_t)sz);
if (!buf) { fclose(fp); return 0; }
got = fread(buf, 1, (size_t)sz, fp);
fclose(fp);
if (got != (size_t)sz) { free(buf); return 0; }
*out = buf;
*out_len = (size_t)sz;
return 1;
}
static const char *base_dir_of(const char *path, uint32_t *out_len) {
const char *slash = strrchr(path, '/');
if (slash) {
*out_len = (uint32_t)(slash - path);
} else {
*out_len = 0;
}
return path;
}
static int parse_path(tg3_model *m, tg3_error_stack *e,
tg3_parse_options *opts,
const char *path, tg3_error_code *err_out) {
uint8_t *buf = NULL;
size_t sz = 0;
uint32_t base_len = 0;
const char *base_dir;
if (!slurp(path, &buf, &sz)) {
fprintf(stderr, " slurp failed: %s\n", path);
return 0;
}
base_dir = base_dir_of(path, &base_len);
*err_out = tg3_parse_auto(m, e, buf, (uint64_t)sz,
base_dir, base_len, opts);
free(buf);
return 1;
}
static int errstack_contains(const tg3_error_stack *e, const char *needle) {
uint32_t i;
size_t nl = strlen(needle);
for (i = 0; i < e->count; ++i) {
const char *m = e->entries[i].message;
if (m && strstr(m, needle) != NULL) return 1;
(void)nl;
}
return 0;
}
/* --- test cases ------------------------------------------------------------ */
#define PASS_OR_RET(label) do { \
fprintf(stdout, " [PASS] %s\n", label); \
return 1; \
} while (0)
#define FAIL(...) do { \
fprintf(stderr, " [FAIL] "); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
goto fail; \
} while (0)
/* TEST_CASE("parse-error", "[parse]") */
static int t_parse_error(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
static const uint8_t bad[] = "bora";
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
err = tg3_parse(&m, &e, bad, (uint64_t)(sizeof(bad) - 1), "", 0, &opts);
if (err == TG3_OK) FAIL("expected parse failure on garbage input");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("parse-error");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("datauri-in-glb", "[issue-79]") */
static int t_datauri_in_glb(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts, "../models/box01.glb", &err)) goto fail;
if (err != TG3_OK) FAIL("box01.glb load failed err=%d", (int)err);
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("datauri-in-glb");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("extension-with-empty-object", "[issue-97]") */
static int t_extension_empty_object(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts, "../models/Extensions-issue97/test.gltf", &err)) goto fail;
if (err != TG3_OK) FAIL("load failed err=%d", (int)err);
if (m.extensions_used_count != 1)
FAIL("extensionsUsed.size %u != 1", m.extensions_used_count);
if (!tg3_str_eq(&m.extensions_used[0], "VENDOR_material_some_ext"))
FAIL("unexpected extensionsUsed[0]");
if (m.materials_count != 1) FAIL("materials.size %u != 1", m.materials_count);
if (!has_extension(&m.materials[0].ext, "VENDOR_material_some_ext"))
FAIL("material missing VENDOR_material_some_ext extension");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("extension-with-empty-object");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("extension-overwrite", "[issue-261]") */
static int t_extension_overwrite(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
uint32_t i;
int has_lights = 0;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/Extensions-overwrite-issue261/issue-261.gltf",
&err)) goto fail;
if (err != TG3_OK) FAIL("load failed err=%d", (int)err);
if (m.extensions_used_count != 3)
FAIL("extensionsUsed.size %u != 3", m.extensions_used_count);
for (i = 0; i < m.extensions_used_count; ++i) {
if (tg3_str_eq(&m.extensions_used[i], "KHR_lights_punctual")) {
has_lights = 1; break;
}
}
if (!has_lights) FAIL("KHR_lights_punctual missing in extensionsUsed");
if (!has_extension(&m.ext, "NV_MDL")) FAIL("model.extensions missing NV_MDL");
if (!has_extension(&m.ext, "KHR_lights_punctual"))
FAIL("model.extensions missing KHR_lights_punctual");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("extension-overwrite");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("invalid-primitive-indices", "[bounds-checking]") */
static int t_invalid_primitive_indices(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/BoundsChecking/invalid-primitive-indices.gltf",
&err)) goto fail;
if (err == TG3_OK)
FAIL("invalid-primitive-indices unexpectedly succeeded");
/* v3 reports TG3_ERR_INVALID_INDEX from validate_indices. v1 reports
* "primitive indices accessor out of bounds" at parse time; either is
* acceptable as long as the parser does not crash and rejects the model. */
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("invalid-primitive-indices");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("invalid-buffer-view-index", "[bounds-checking]") */
static int t_invalid_buffer_view_index(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/BoundsChecking/invalid-buffer-view-index.gltf",
&err)) goto fail;
if (err == TG3_OK) FAIL("invalid-buffer-view-index unexpectedly succeeded");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("invalid-buffer-view-index");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("invalid-buffer-index", "[bounds-checking]") */
static int t_invalid_buffer_index(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/BoundsChecking/invalid-buffer-index.gltf",
&err)) goto fail;
if (err == TG3_OK) FAIL("invalid-buffer-index unexpectedly succeeded");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("invalid-buffer-index");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("glb-invalid-length", "[bounds-checking]") */
static int t_glb_invalid_length(void) {
/* 'glTF' magic, version=2, length=0x0000666c (way larger than provided
* data), JSON chunk with empty {}. */
static const unsigned char glb[] = "glTF"
"\x02\x00\x00\x00" "\x6c\x66\x00\x00"
"\x02\x00\x00\x00" "\x4a\x53\x4f\x4e{}";
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
err = tg3_parse_glb(&m, &e, glb, (uint64_t)(sizeof(glb) - 1), "", 0, &opts);
if (err == TG3_OK) FAIL("invalid-length GLB unexpectedly accepted");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("glb-invalid-length");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("integer-out-of-bounds", "[bounds-checking]") */
static int t_integer_out_of_bounds(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/BoundsChecking/integer-out-of-bounds.gltf",
&err)) goto fail;
if (err == TG3_OK) FAIL("integer-out-of-bounds unexpectedly succeeded");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("integer-out-of-bounds");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("pbr-khr-texture-transform", "[material]") */
static int t_pbr_khr_texture_transform(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
const tg3_extension *ext;
const tg3_value *scale, *s0, *s1;
double v0, v1;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/Cube-texture-ext/Cube-textransform.gltf",
&err)) goto fail;
if (err != TG3_OK) FAIL("load failed err=%d", (int)err);
if (m.materials_count != 2) FAIL("materials.size %u != 2", m.materials_count);
ext = find_extension(&m.materials[0].emissive_texture.ext,
"KHR_texture_transform");
if (!ext) FAIL("KHR_texture_transform missing on emissiveTexture");
if (ext->value.type != TG3_VALUE_OBJECT)
FAIL("KHR_texture_transform value not an object");
scale = value_get(&ext->value, "scale");
if (!scale || scale->type != TG3_VALUE_ARRAY || scale->array_count != 2)
FAIL("scale not an array of 2");
s0 = &scale->array_data[0];
s1 = &scale->array_data[1];
v0 = (s0->type == TG3_VALUE_INT) ? (double)s0->int_val : s0->real_val;
v1 = (s1->type == TG3_VALUE_INT) ? (double)s1->int_val : s1->real_val;
if (v0 != 1.0) FAIL("scale[0] %g != 1.0", v0);
if (v1 != -1.0) FAIL("scale[1] %g != -1.0", v1);
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("pbr-khr-texture-transform");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("image-uri-spaces", "[issue-236]") */
static int t_image_uri_spaces(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
/* Single-spaces variant. */
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/CubeImageUriSpaces/CubeImageUriSpaces.gltf",
&err)) goto fail;
if (err != TG3_OK) FAIL("CubeImageUriSpaces load failed err=%d", (int)err);
if (m.images_count != 1) FAIL("images.size %u != 1", m.images_count);
if (!tg3_str_contains(&m.images[0].uri, ' '))
FAIL("image.uri does not contain a space");
tg3_model_free(&m); tg3_error_stack_free(&e);
/* Multiple-spaces variant. */
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/CubeImageUriSpaces/CubeImageUriMultipleSpaces.gltf",
&err)) goto fail2;
if (err != TG3_OK) FAIL("MultipleSpaces load failed err=%d", (int)err);
if (m.images_count != 1) FAIL("images.size %u != 1", m.images_count);
if (m.images[0].uri.len < 2 || m.images[0].uri.data[0] != ' ')
FAIL("image.uri does not start with a space");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("image-uri-spaces");
fail2:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("empty-skeleton-id", "[issue-321]") */
static int t_empty_skeleton_id(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/regression/unassigned-skeleton.gltf",
&err)) goto fail;
if (err != TG3_OK) FAIL("load failed err=%d", (int)err);
if (m.skins_count != 1) FAIL("skins.size %u != 1", m.skins_count);
if (m.skins[0].skeleton != -1)
FAIL("skin.skeleton %d != -1", m.skins[0].skeleton);
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("empty-skeleton-id");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("filesize-check", "[issue-416]") */
static int t_filesize_check(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
opts.max_external_file_size = 10; /* 10 bytes — texture image will exceed */
if (!parse_path(&m, &e, &opts, "../models/Cube/Cube.gltf", &err)) goto fail;
if (err == TG3_OK)
FAIL("expected load failure due to oversized external file");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("filesize-check");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("load-issue-416-model", "[issue-416]") */
static int t_load_issue_416_model(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts, "issue-416.gltf", &err)) goto fail;
/* External file is missing, but the parser should still accept the glTF
* structurally. v1 returns true; v3 returns TG3_OK or a non-fatal error
* about the missing file. */
(void)err; /* tolerate either outcome */
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("load-issue-416-model");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("zero-sized-bin-chunk-glb", "[issue-440]") */
static int t_zero_sized_bin_chunk_glb(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts,
"../models/regression/zero-sized-bin-chunk-issue-440.glb",
&err)) goto fail;
if (err != TG3_OK) FAIL("zero-sized-bin-chunk failed err=%d", (int)err);
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("zero-sized-bin-chunk-glb");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("images-as-is", "[issue-487]") */
static int t_images_as_is(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
uint32_t i;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
opts.images_as_is = 1;
if (!parse_path(&m, &e, &opts, "../models/Cube/Cube.gltf", &err)) goto fail;
if (err != TG3_OK) FAIL("Cube load failed err=%d", (int)err);
if (m.images_count == 0) FAIL("no images parsed");
for (i = 0; i < m.images_count; ++i) {
if (m.images[i].as_is != 1) FAIL("image[%u].as_is != 1", i);
if (m.images[i].uri.len == 0) FAIL("image[%u].uri empty", i);
}
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("images-as-is");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("inverse-bind-matrices-optional", "[issue-492]") */
static int t_ibm_optional(void) {
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
if (!parse_path(&m, &e, &opts, "issue-492.glb", &err)) goto fail;
if (err != TG3_OK) FAIL("issue-492.glb load failed err=%d", (int)err);
/* No "error" entries (warnings are not errors). */
if (errstack_contains(&e, "error"))
FAIL("unexpected error message present");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("inverse-bind-matrices-optional");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* TEST_CASE("default-material", "[issue-459]") — black-box parse of a
* minimal model with a default material; verify v3 populates the same
* default-PBR field values v1 documents. */
static int t_default_material(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"materials\":[{}]}";
tg3_model m; tg3_error_stack e; tg3_parse_options opts;
tg3_error_code err;
const tg3_material *mat;
tg3_error_stack_init(&e); tg3_parse_options_init(&opts);
err = tg3_parse(&m, &e, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_OK) FAIL("minimal default-material parse failed err=%d", (int)err);
if (m.materials_count != 1) FAIL("materials.size %u != 1", m.materials_count);
mat = &m.materials[0];
if (!tg3_str_eq(&mat->alpha_mode, "OPAQUE")) FAIL("alpha_mode default mismatch");
if (mat->alpha_cutoff != 0.5) FAIL("alpha_cutoff default %g != 0.5", mat->alpha_cutoff);
if (mat->double_sided != 0) FAIL("double_sided default != 0");
if (mat->emissive_factor[0] != 0.0 ||
mat->emissive_factor[1] != 0.0 ||
mat->emissive_factor[2] != 0.0) FAIL("emissive_factor default mismatch");
if (mat->pbr_metallic_roughness.base_color_factor[0] != 1.0 ||
mat->pbr_metallic_roughness.base_color_factor[1] != 1.0 ||
mat->pbr_metallic_roughness.base_color_factor[2] != 1.0 ||
mat->pbr_metallic_roughness.base_color_factor[3] != 1.0)
FAIL("base_color_factor default mismatch");
if (mat->pbr_metallic_roughness.metallic_factor != 1.0)
FAIL("metallic_factor default %g != 1.0",
mat->pbr_metallic_roughness.metallic_factor);
if (mat->pbr_metallic_roughness.roughness_factor != 1.0)
FAIL("roughness_factor default %g != 1.0",
mat->pbr_metallic_roughness.roughness_factor);
if (mat->normal_texture.index != -1) FAIL("normal_texture.index != -1");
if (mat->occlusion_texture.index != -1) FAIL("occlusion_texture.index != -1");
if (mat->emissive_texture.index != -1) FAIL("emissive_texture.index != -1");
tg3_model_free(&m); tg3_error_stack_free(&e);
PASS_OR_RET("default-material");
fail:
tg3_model_free(&m); tg3_error_stack_free(&e);
return 0;
}
/* --- main ------------------------------------------------------------------ */
int main(void) {
struct { const char *name; int (*fn)(void); } cases[] = {
{"parse-error", t_parse_error},
{"datauri-in-glb", t_datauri_in_glb},
{"extension-with-empty-object", t_extension_empty_object},
{"extension-overwrite", t_extension_overwrite},
{"invalid-primitive-indices", t_invalid_primitive_indices},
{"invalid-buffer-view-index", t_invalid_buffer_view_index},
{"invalid-buffer-index", t_invalid_buffer_index},
{"glb-invalid-length", t_glb_invalid_length},
{"integer-out-of-bounds", t_integer_out_of_bounds},
{"pbr-khr-texture-transform", t_pbr_khr_texture_transform},
{"image-uri-spaces", t_image_uri_spaces},
{"empty-skeleton-id", t_empty_skeleton_id},
{"filesize-check", t_filesize_check},
{"load-issue-416-model", t_load_issue_416_model},
{"zero-sized-bin-chunk-glb", t_zero_sized_bin_chunk_glb},
{"images-as-is", t_images_as_is},
{"inverse-bind-matrices-optional", t_ibm_optional},
{"default-material", t_default_material},
};
size_t total = sizeof(cases) / sizeof(cases[0]);
size_t passed = 0;
size_t i;
for (i = 0; i < total; ++i) {
fprintf(stdout, "RUN %s\n", cases[i].name);
if (cases[i].fn()) ++passed;
}
fprintf(stdout, "\n=== v1 port: %zu/%zu passed ===\n", passed, total);
return passed == total ? 0 : 1;
}

View File

@@ -1,75 +0,0 @@
#include <stddef.h>
#include <stdint.h>
static union {
uint64_t align;
unsigned char bytes[512 * 1024];
} test_heap;
static size_t test_heap_used;
static void *test_malloc(size_t size) {
size_t total = (size + sizeof(size_t) + 7u) & ~(size_t)7u;
if (test_heap_used + total > sizeof(test_heap.bytes)) return 0;
{
unsigned char *base = test_heap.bytes + test_heap_used;
*((size_t *)base) = size;
test_heap_used += total;
return base + sizeof(size_t);
}
}
static void *test_realloc(void *ptr, size_t size) {
unsigned char *old_base;
size_t old_size;
unsigned char *new_ptr;
size_t n;
size_t i;
if (!ptr) return test_malloc(size);
old_base = (unsigned char *)ptr - sizeof(size_t);
old_size = *((size_t *)old_base);
new_ptr = (unsigned char *)test_malloc(size);
if (!new_ptr) return 0;
n = old_size < size ? old_size : size;
for (i = 0; i < n; ++i) new_ptr[i] = ((unsigned char *)ptr)[i];
return new_ptr;
}
static void test_free(void *ptr) {
(void)ptr;
}
#define TINYGLTF3_NO_STDLIB
#define TINYGLTF3_MALLOC(sz) test_malloc(sz)
#define TINYGLTF3_REALLOC(ptr, sz) test_realloc((ptr), (sz))
#define TINYGLTF3_FREE(ptr) test_free(ptr)
#define TINYGLTF3_IMPLEMENTATION
#include "tiny_gltf_v3.h"
static int streq(tg3_str s, const char *lit, uint32_t len) {
uint32_t i;
if (!s.data || s.len != len) return 0;
for (i = 0; i < len; ++i) {
if (s.data[i] != lit[i]) return 0;
}
return 1;
}
int main(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"nodes\":[{\"name\":\"free\"}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse_auto(&model, &errors, json, (uint64_t)(sizeof(json) - 1),
"", 0, &opts);
if (err != TG3_OK) return 1;
if (model.nodes_count != 1) return 3;
if (!streq(model.nodes[0].name, "free", 4)) return 4;
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}

View File

@@ -1,165 +0,0 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define TINYGLTF_JSON_C_IMPLEMENTATION
#include "tinygltf_json_c.h"
static uint64_t dbl_bits(double v) {
uint64_t bits;
memcpy(&bits, &v, sizeof(bits));
return bits;
}
static double dbl_from_bits(uint64_t bits) {
double v;
memcpy(&v, &bits, sizeof(v));
return v;
}
static int check_stringify(const char *json, const char *expected) {
tg3json_value v;
const char *err = NULL;
char *out;
size_t out_len = 0;
int ok = 1;
if (!tg3json_parse(json, json + strlen(json), 128, &v, &err)) {
fprintf(stderr, "parse failed for %s at %td\n", json, err ? err - json : -1);
return 0;
}
out = tg3json_stringify(&v, &out_len);
if (!out || strcmp(out, expected) != 0) {
fprintf(stderr, "stringify(%s) = %s, expected %s\n",
json, out ? out : "(null)", expected);
ok = 0;
}
if (out) TINYGLTF_JSON_FREE(out);
tg3json_value_free(&v);
return ok;
}
static int check_parse_rejects(const char *json) {
tg3json_value v;
const char *err = NULL;
if (tg3json_parse(json, json + strlen(json), 128, &v, &err)) {
fprintf(stderr, "parse unexpectedly accepted %s\n", json);
tg3json_value_free(&v);
return 0;
}
return 1;
}
static int check_roundtrip(const char *json) {
tg3json_value a;
tg3json_value b;
const char *err = NULL;
char *out;
size_t out_len = 0;
int ok = 1;
if (!tg3json_parse(json, json + strlen(json), 128, &a, &err)) return 0;
out = tg3json_stringify(&a, &out_len);
if (!out || !tg3json_parse(out, out + out_len, 128, &b, &err)) {
ok = 0;
} else if (a.type != TG3JSON_REAL ||
!((b.type == TG3JSON_REAL && dbl_bits(a.u.real) == dbl_bits(b.u.real)) ||
(b.type == TG3JSON_INT && dbl_bits(a.u.real) == dbl_bits((double)b.u.integer)))) {
fprintf(stderr, "roundtrip changed bits: %s -> %s\n", json, out);
ok = 0;
}
if (out) TINYGLTF_JSON_FREE(out);
tg3json_value_free(&a);
tg3json_value_free(&b);
return ok;
}
static int check_parse_bits(const char *json, uint64_t expected_bits) {
tg3json_value v;
const char *err = NULL;
if (!tg3json_parse(json, json + strlen(json), 128, &v, &err)) {
fprintf(stderr, "parse failed for %s at %td\n", json, err ? err - json : -1);
return 0;
}
if (v.type != TG3JSON_REAL || dbl_bits(v.u.real) != expected_bits) {
fprintf(stderr, "parse bits mismatch: %s -> 0x%llx, expected 0x%llx\n",
json, (unsigned long long)(v.type == TG3JSON_REAL ? dbl_bits(v.u.real) : 0),
(unsigned long long)expected_bits);
tg3json_value_free(&v);
return 0;
}
tg3json_value_free(&v);
return 1;
}
static int check_parse_float32(void) {
static const char json[] = "0.10000000149011612";
tg3json_parse_options opts;
tg3json_value v;
const char *err = NULL;
double expected = (double)(float)0.10000000149011612;
memset(&opts, 0, sizeof(opts));
opts.parse_float32 = 1;
if (!tg3json_parse_n_opts(json, strlen(json), &opts, &v, &err)) {
fprintf(stderr, "parse_float32 parse failed\n");
return 0;
}
if (v.type != TG3JSON_REAL || dbl_bits(v.u.real) != dbl_bits(expected)) {
fprintf(stderr, "parse_float32 did not round through float\n");
tg3json_value_free(&v);
return 0;
}
tg3json_value_free(&v);
return 1;
}
static int check_nonfinite_stringifies_to_null(void) {
tg3json_value v;
char *out;
size_t len = 0;
int ok;
tg3json_value_init_real(&v, dbl_from_bits(0x7ff0000000000000ULL));
out = tg3json_stringify(&v, &len);
ok = out && strcmp(out, "null") == 0;
if (!ok) fprintf(stderr, "inf stringify = %s\n", out ? out : "(null)");
if (out) TINYGLTF_JSON_FREE(out);
tg3json_value_free(&v);
if (!ok) return 0;
tg3json_value_init_real(&v, dbl_from_bits(0x7ff8000000000001ULL));
out = tg3json_stringify(&v, &len);
ok = out && strcmp(out, "null") == 0;
if (!ok) fprintf(stderr, "nan stringify = %s\n", out ? out : "(null)");
if (out) TINYGLTF_JSON_FREE(out);
tg3json_value_free(&v);
return ok;
}
int main(void) {
int ok = 1;
ok = check_stringify("1.0", "1") && ok;
ok = check_stringify("-1.0", "-1") && ok;
ok = check_stringify("0.1", "0.1") && ok;
ok = check_stringify("0.0001", "0.0001") && ok;
ok = check_stringify("0.00001", "1e-5") && ok;
ok = check_stringify("1000000000000000.0", "1000000000000000") && ok;
ok = check_stringify("10000000000000000.0", "1e16") && ok;
ok = check_roundtrip("1.2345678901234567") && ok;
ok = check_roundtrip("2.2250738585072014e-308") && ok;
ok = check_roundtrip("5e-324") && ok;
ok = check_roundtrip("-5e-324") && ok;
ok = check_roundtrip("9007199254740993.0") && ok;
ok = check_parse_bits("1.23456789012345678901", 0x3ff3c0ca428c59fbULL) && ok;
ok = check_parse_bits("1.234567890123456789012345678901234567890e-100",
0x2b31482fe620c5d2ULL) && ok;
ok = check_parse_bits("1.7976931348623157e308", 0x7fefffffffffffffULL) && ok;
ok = check_parse_float32() && ok;
ok = check_nonfinite_stringifies_to_null() && ok;
ok = check_parse_rejects("+1") && ok;
ok = check_parse_rejects("01") && ok;
ok = check_parse_rejects("1.") && ok;
ok = check_parse_rejects("1e") && ok;
ok = check_parse_rejects("1e400") && ok;
ok = check_parse_rejects("-1e400") && ok;
ok = check_parse_rejects("1.7976931348623159e308") && ok;
ok = check_parse_rejects("[1,]") && ok;
return ok ? 0 : 1;
}

View File

@@ -1,83 +0,0 @@
# tests/v3/fuzzer/Makefile — Build libFuzzer harnesses for tinygltf v3
#
# Requires: clang/clang++ with libFuzzer support
#
# Targets:
# make — build both harnesses with ASan + UBSan
# make run — run the dedicated pure-C v3 harness
# make run-cpp — run the legacy header-implementation harness
# make seed — generate seed corpus from test models
# make clean — remove binaries and corpus
CC = clang
CXX = clang++
CCFLAGS = -g -O1 -std=c11
CXXFLAGS = -g -O1 -std=c++17 -fno-rtti -fno-exceptions
SANITIZE = -fsanitize=fuzzer,address,undefined
INCLUDES = -I../../..
FUZZER = fuzz_gltf_v3
FUZZER_C = fuzz_gltf_v3_c
CORPUS = corpus
ARTIFACTS = artifacts
# Fuzzer runtime options
MAX_LEN ?= 65536
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
MAX_TIME ?= 0
FUZZ_ENV ?= LSAN_OPTIONS=detect_leaks=0
.PHONY: all run run-cpp seed clean
all: $(FUZZER) $(FUZZER_C)
$(FUZZER): fuzz_gltf_v3.cc ../../../tiny_gltf_v3.h ../../../tiny_gltf_v3.c ../../../tinygltf_json_c.h
$(CXX) $(CXXFLAGS) $(SANITIZE) $(INCLUDES) -o $@ $<
$(FUZZER_C): fuzz_gltf_v3_c.c ../../../tiny_gltf_v3.h ../../../tiny_gltf_v3.c ../../../tinygltf_json_c.h
$(CC) $(CCFLAGS) $(SANITIZE) $(INCLUDES) -o $@ $< ../../../tiny_gltf_v3.c
run: $(FUZZER_C) | $(CORPUS) $(ARTIFACTS)
$(FUZZ_ENV) ./$(FUZZER_C) $(CORPUS) \
-artifact_prefix=$(ARTIFACTS)/ \
-max_len=$(MAX_LEN) \
-jobs=$(JOBS) \
-workers=$(JOBS) \
$(if $(filter-out 0,$(MAX_TIME)),-max_total_time=$(MAX_TIME))
run-cpp: $(FUZZER) | $(CORPUS) $(ARTIFACTS)
$(FUZZ_ENV) ./$(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) $(FUZZER_C)
rm -rf $(CORPUS) $(ARTIFACTS)

View File

@@ -1,110 +0,0 @@
/*
* 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;
}

View File

@@ -1,39 +0,0 @@
#include "tiny_gltf_v3.h"
#include <stddef.h>
#include <stdint.h>
static const uint64_t FUZZ_MEMORY_BUDGET = 64ULL * 1024 * 1024;
typedef tg3_error_code (*tg3_fuzz_parse_fn)(tg3_model *, tg3_error_stack *,
const uint8_t *, uint64_t, const char *, uint32_t, const tg3_parse_options *);
static void tg3_fuzz_run(tg3_fuzz_parse_fn fn, int parse_float32,
const uint8_t *data, size_t size) {
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.memory.memory_budget = FUZZ_MEMORY_BUDGET;
opts.parse_float32 = parse_float32;
fn(&model, &errors, data, (uint64_t)size, "", 0, &opts);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size == 0) return 0;
switch (data[0] % 4) {
case 0: tg3_fuzz_run(tg3_parse_auto, 0, data + 1, size - 1); break;
case 1: tg3_fuzz_run(tg3_parse, 0, data + 1, size - 1); break;
case 2: tg3_fuzz_run(tg3_parse_glb, 0, data + 1, size - 1); break;
case 3: tg3_fuzz_run(tg3_parse_auto, 1, data + 1, size - 1); break;
}
return 0;
}

View File

@@ -1,8 +0,0 @@
{
"asset": {"version": "2.0"},
"buffers": [{"byteLength": 4}],
"bufferViews": [
{"buffer": 0, "byteOffset": 0, "byteLength": 4, "byteStride": -1}
],
"scenes": [{"nodes": []}]
}

View File

@@ -1,9 +0,0 @@
{
"asset": {"version": "2.0"},
"buffers": [{"byteLength": 4}],
"bufferViews": [{"buffer": 0, "byteOffset": 0, "byteLength": 4}],
"accessors": [
{"bufferView": 1000000, "byteOffset": 0, "componentType": 5121, "count": 1, "type": "SCALAR"}
],
"scenes": [{"nodes": []}]
}

View File

@@ -1,19 +0,0 @@
{
"asset": {"version": "2.0"},
"nodes": [
{"extensions": {"KHR_audio": {"emitter": 2147483647},
"MSFT_lod": {"ids": [-1, 9999]}}}
],
"materials": [
{"extensions": {"MSFT_lod": {"ids": [-5]}}}
],
"scenes": [
{"extensions": {"KHR_audio": {"emitters": [12345]}}}
],
"extensions": {
"KHR_audio": {
"sources": [{"bufferView": -1}],
"emitters": [{"source": 99999}]
}
}
}

View File

@@ -1,14 +0,0 @@
{
"asset": {"version": "2.0"},
"buffers": [
{"uri": "../../../../../../../../tmp/tg3-poc-secret.txt", "byteLength": 32}
],
"bufferViews": [
{"buffer": 0, "byteOffset": 0, "byteLength": 32}
],
"accessors": [
{"bufferView": 0, "byteOffset": 0, "componentType": 5121, "count": 32, "type": "SCALAR"}
],
"scenes": [{"nodes": []}],
"scene": 0
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff