Compare commits

..

1 Commits

79 changed files with 9114 additions and 38837 deletions

13
.github/FUNDING.yml vendored
View File

@@ -1,13 +0,0 @@
# These are supported funding model platforms
github: syoyo
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1 +0,0 @@
blank_issues_enabled: false

View File

@@ -1,30 +0,0 @@
---
name: Issue report
about: Create a report
title: ''
labels: ''
assignees: ''
---
**Describe the issue**
A clear and concise description of what the issue is.
**To Reproduce**
- OS
- Compiler, compiler version, compile options
- Please attach minimal and reproducible .glTF file if you got an issue related to .glTF data
**Expected behaviour**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

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

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

View File

@@ -1,180 +0,0 @@
name: C/C++ CI
on: [push, pull_request]
jobs:
# gcc4.8 is too old and ubuntu-18.04 image is not supported in GitHub Actions anymore,
# so disable this build.
## compile with older gcc4.8
#build-gcc48:
# runs-on: ubuntu-18.04
# name: Build with gcc 4.8
# steps:
# - name: Checkout
# uses: actions/checkout@v5
# - name: Build
# run: |
# sudo apt-get update
# sudo apt-get install -y build-essential
# sudo apt-get install -y gcc-4.8 g++-4.8
# g++-4.8 -std=c++11 -o loader_example loader_example.cc
# - name: NoexceptBuild
# run: |
# g++-4.8 -DTINYGLTF_NOEXCEPTION -std=c++11 -o loader_example loader_example.cc
# - name: RapidjsonBuild
# run: |
# git clone https://github.com/Tencent/rapidjson
# g++-4.8 -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -o loader_example loader_example.cc
# compile with mingw gcc cross
build-mingw-cross:
runs-on: ubuntu-latest
name: Build with MinGW gcc cross
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Build
run: |
sudo apt-get update
sudo apt-get install -y build-essential
sudo apt-get install -y mingw-w64
x86_64-w64-mingw32-g++ -std=c++11 -o loader_example loader_example.cc
# Windows(x64) + Visual Studio 2022 build
# Assume windows-latest have VS2022 installed
build-windows-msvc:
runs-on: windows-latest
name: Build for Windows(x64, MSVC)
# Use system installed cmake
# https://help.github.com/en/actions/reference/software-installed-on-github-hosted-runners
steps:
- name: Checkout
uses: actions/checkout@v5
- 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 ..
cd ..
- name: Build
run: cmake --build build --config Release
build-linux:
runs-on: ubuntu-latest
name: Buld with gcc
steps:
- uses: actions/checkout@v5
- name: build
run: |
g++ -std=c++11 -o loader_example loader_example.cc
- name: test
run: |
./loader_example models/Cube/Cube.gltf
- name: tests
run: |
cd tests
g++ -I../ -std=c++11 -g -O0 -o tester tester.cc
./tester
cd ..
- name: noexcept_tests
run: |
cd tests
g++ -DTINYGLTF_NOEXCEPTION -I../ -std=c++11 -g -O0 -o tester_noexcept tester.cc
./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
cd ..
build-rapidjson-linux:
runs-on: ubuntu-latest
name: Buld with gcc + rapidjson
steps:
- uses: actions/checkout@v5
- name: build
run: |
git clone https://github.com/Tencent/rapidjson
g++ -v
g++ -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -o loader_example loader_example.cc
- name: loader_example_test
run: |
./loader_example models/Cube/Cube.gltf
- name: tests
run: |
cd tests
g++ -DTINYGLTF_USE_RAPIDJSON -I../rapidjson/include/rapidjson -I../ -std=c++11 -g -O0 -o tester tester.cc
./tester
cd ..
- name: noexcept_tests
run: |
cd tests
g++ -DTINYGLTF_USE_RAPIDJSON -I../rapidjson/include/rapidjson -DTINYGLTF_NOEXCEPTION -I../ -std=c++11 -g -O0 -o tester_noexcept tester.cc
./tester_noexcept
cd ..
# Cross-compile for aarch64 linux target
build-cross-aarch64:
runs-on: ubuntu-latest
name: Build on cross aarch64
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Build
run: |
sudo apt-get update
sudo apt-get install -y build-essential
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
git clone https://github.com/Tencent/rapidjson
aarch64-linux-gnu-g++ -DTINYGLTF_USE_RAPIDJSON -I./rapidjson/include/rapidjson -std=c++11 -g -O0 -o loader_example loader_example.cc
# macOS clang
build-macos:
runs-on: macos-latest
name: Build on macOS
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Build
run: |
clang++ -std=c++11 -g -O0 -o loader_example loader_example.cc
./loader_example models/Cube/Cube.gltf
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,513 +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
- 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
- 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
- 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
- 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 ..
- 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 ..
- 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
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
cmake --build build
- name: Run loader_example
run: |
./build/loader_example models/Cube/Cube.gltf
- name: Run tests
run: ctest --test-dir build --output-on-failure
# Special Configuration: RapidJSON Backend
linux-rapidjson:
runs-on: ubuntu-latest
name: Linux x64 (GCC) - RapidJSON Backend
steps:
- name: Checkout
uses: actions/checkout@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 -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

View File

@@ -1,72 +0,0 @@
# 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@v5
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
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@v3
# 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@v3

View File

@@ -1,45 +0,0 @@
name: MSYS2 MinGW-w64 Windows 64bit Build
on:
push:
branches:
- release
- devel
paths:
- 'tiny_gltf.*'
- 'CMakeLists.txt'
- '.github/workflows/mingw-w64-msys2.yml'
pull_request:
workflow_dispatch:
jobs:
mingw-w64-msys2-build:
name: MSYS2 MinGW-w64 Windows Build
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@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
- name: Build
run: |
cmake --build build

26
.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
@@ -72,26 +67,5 @@ imgui.ini
loader_example
tests/tester
tests/tester_noexcept
tests/tester_intensive_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
!examples/build-gltf/Makefile
!examples/raytrace/cornellbox_suzanne.obj
!tests/Makefile
!tools/windows/premake5.exe

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

51
.travis.yml Normal file
View File

@@ -0,0 +1,51 @@
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"
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
- make
- ./tester
- ./tester_noexcept
- cd ../examples/raytrace
- ../../premake5 gmake
- make

View File

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

180
README.md
View File

@@ -1,114 +1,25 @@
# 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 *Security model* below and 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);
```
### Security model (v3 C runtime)
The v3 C runtime is built for processing **untrusted glTF/GLB input** (server-side asset pipelines, user uploads, etc.) and ships hardened by default:
- **URI sanitization** — external buffer/image URIs are rejected before any filesystem call if they are empty, contain NUL bytes, begin with `/` or `\`, look like a Windows drive prefix (`X:`), or contain a `..` segment. Production callers SHOULD still provide a custom `tg3_fs_callbacks.read_file` that confines reads to a known directory (e.g. via `openat` plus a `realpath` prefix check) when the input is attacker-controlled.
- **Index bounds validation** — every `int32_t` index field populated from JSON (accessor.bufferView, primitive.indices/material/attributes, scene.nodes[], skin.joints[], animation channel/sampler refs, KHR_audio + MSFT_lod refs, …) is checked after the structural parse. Out-of-range indices produce `TG3_ERR_INVALID_INDEX`. Default `tg3_parse_options.validate_indices = 1`; set to `0` only when you need raw round-trip and have your own validator.
- **Strict numeric range checks** — JSON numbers feeding integer fields go through finite/round-trip-validated coercion (`tg3__json_number_to_int32` / `_uint64`). `byteStride` is restricted to 0 or [4, 252].
- **Memory budget** — the arena is capped at `TINYGLTF3_MAX_MEMORY_BYTES` (1 GB by default; configurable via `tg3_memory_config`).
- **Image decoding off by default** — the parser does not decode image bytes; use `tg3_parse_options.images_as_is = 1` to skip any decoder entirely when handling untrusted input.
- **Error message lifetime** — error strings on `tg3_error_stack` are arena-allocated and remain valid until `tg3_model_free()`. Read or copy them BEFORE freeing the model.
See the `Security Considerations` block at the top of `tiny_gltf_v3.h` for the authoritative threat-model summary.
### 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.
If you are looking for old, C++03 version, please use `devel-picojson` branch.
## 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**.
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).
- v2.5.0 Add SetPreserveImageChannels() option to load image data as is.
- v2.4.0 Experimental RapidJSON support. Experimental C++14 support(C++14 may give better performance)
- v2.3.0 Modified Material representation according to glTF 2.0 schema(and introduced TextureInfo class)
- v2.2.0 release(Support loading 16bit PNG. Sparse accessor support)
- v2.1.0 release(Draco decoding support)
- v2.1.0 release(Draco support)
- v2.0.0 release(22 Aug, 2018)!
### Branches
* `sajson` : Use sajson to parse JSON. Parsing only but faster compile time(2x reduction compared to json.hpp and RapidJson), but not well maintained.
## Builds
![C/C++ CI](https://github.com/syoyo/tinygltf/workflows/C/C++%20CI/badge.svg)
[![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)
## Features
Probably mostly feature-complete. Last missing feature is Draco encoding: https://github.com/syoyo/tinygltf/issues/207
* Written in portable C++. C++-11 with STL dependency only.
* [x] macOS + clang(LLVM)
* [x] iOS + clang
@@ -122,11 +33,8 @@ Probably mostly feature-complete. Last missing feature is Draco encoding: https:
* Moderate parsing time and memory consumption.
* glTF specification v2.0.0
* [x] ASCII glTF
* [x] Load
* [x] Save
* [x] Binary glTF(GLB)
* [x] Load
* [x] Save(.bin embedded .glb)
* [x] PBR material description
* Buffers
* [x] Parse BASE64 encoded embedded buffer data(DataURI).
* [x] Load `.bin` file.
@@ -146,7 +54,6 @@ Probably mostly feature-complete. Last missing feature is Draco encoding: https:
* [x] Image save
* Extensions
* [x] Draco mesh decoding
* [ ] Draco mesh encoding
## Note on extension property
@@ -159,18 +66,19 @@ In extension(`ExtensionMap`), JSON number value is parsed as int or float(number
* [glview](examples/glview) : Simple glTF geometry viewer.
* [validator](examples/validator) : Simple glTF validator with JSON schema.
* [basic](examples/basic) : Basic glTF viewer with texturing support.
* [build-gltf](examples/build-gltf) : Build simple glTF scene from a scratch.
### WASI/WASM build
Users who want to run TinyGLTF securely and safely(e.g. need to handle malcious glTF file to serve online glTF conver),
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
* Your projects here! (Please send PR)
## TODOs
* [ ] Robust URI decoding/encoding. https://github.com/syoyo/tinygltf/issues/369
* [ ] Write C++ code generator which emits C++ code from JSON schema for robust parsing.
* [ ] Mesh Compression/decompression(Open3DGC, etc)
* [x] Load Draco compressed mesh
* [ ] Save Draco compressed mesh
@@ -181,10 +89,6 @@ WASI build example is located in [wasm](wasm) .
* [ ] 16bit PNG support in Serialization
* [ ] Write example and tests for `animation` and `skin`
### Optional
* [ ] Write C++ code generator which emits C++ code from JSON schema for robust parsing?
## Licenses
TinyGLTF is licensed under MIT license.
@@ -217,10 +121,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());
@@ -231,14 +134,11 @@ if (!err.empty()) {
}
if (!ret) {
printf("Failed to parse glTF: %s\n", filename.c_str());
printf("Failed to parse glTF\n");
return -1;
}
```
#### Loader options
* `TinyGLTF::SetPreserveimageChannels(bool onoff)`. `true` to preserve image channels as stored in image file for loaded image. `false` by default for backward compatibility(image channels are widen to `RGBA` 4 channels). Effective only when using builtin image loader(STB image loader).
## Compile options
* `TINYGLTF_NOEXCEPTION` : Disable C++ exception in JSON parsing. You can use `-fno-exceptions` or by defining the symbol `JSON_NOEXCEPTION` and `TINYGLTF_NOEXCEPTION` to fully remove C++ exception codes when compiling TinyGLTF.
@@ -248,44 +148,19 @@ if (!ret) {
* `TINYGLTF_ANDROID_LOAD_FROM_ASSETS`: Load all files from packaged app assets instead of the regular file system. **Note:** You must pass a valid asset manager from your android app to `tinygltf::asset_manager` beforehand.
* `TINYGLTF_ENABLE_DRACO`: Enable Draco compression. User must provide include path and link correspnding libraries in your project file.
* `TINYGLTF_NO_INCLUDE_JSON `: Disable including `json.hpp` 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_RAPIDJSON `: Disable including RapidJson's header files 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 `: 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.
## CMake options
You can add tinygltf using `add_subdirectory` feature.
If you add tinygltf to your project using `add_subdirectory`, it would be better to set `TINYGLTF_HEADER_ONLY` on(just add an include path to tinygltf) and `TINYGLTF_INSTALL` off(Which does not install tinygltf files).
```
// Your project's CMakeLists.txt
...
set(TINYGLTF_HEADER_ONLY ON CACHE INTERNAL "" FORCE)
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
* Buffers.
* [ ] Buffers.
* [x] To file
* [x] Embedded
* [ ] Draco compressed?
* [x] Images
* [x] To file
* [x] Embedded
* Binary(.glb)
* [x] .bin embedded single .glb
* [ ] External .bin
* [ ] Binary(.glb)
## Running tests.
@@ -293,7 +168,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE "/path/to/tinygltf")
#### Setup
Python required.
Python 2.6 or 2.7 required.
Git clone https://github.com/KhronosGroup/glTF-Sample-Models to your local dir.
#### Run parsing test
@@ -313,17 +188,8 @@ $ ./tester
$ ./tester_noexcept
```
### Fuzzing tests
See `tests/fuzzer` for details.
After running fuzzer on Ryzen9 3950X a week, at least `LoadASCIIFromString` looks safe except for out-of-memory error in Fuzzer.
We may be better to introduce bounded memory size checking when parsing glTF data.
## Third party licenses
* json.hpp : Licensed under the MIT License <http://opensource.org/licenses/MIT>. Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>.
* stb_image : Public domain.
* catch : Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. Distributed under the Boost Software License, Version 1.0.
* RapidJSON : Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. http://rapidjson.org/
* dlib(uridecode, uriencode) : Copyright (C) 2003 Davis E. King Boost Software License 1.0. http://dlib.net/dlib/server/server_http.cpp.html

View File

@@ -1,4 +0,0 @@
# Security Policy
This project manages CVE assignments exclusively through
GitHub Security Advisories.

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,396 +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) {
BenchResult r = {};
r.filename = filename;
r.iterations = iterations;
/* Read file into memory */
FILE *f = fopen(filename, "rb");
if (!f) {
fprintf(stderr, "ERROR: Cannot open '%s'\n", filename);
return r;
}
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
if (sz <= 0) { fclose(f); return r; }
std::vector<uint8_t> data((size_t)sz);
size_t rd = fread(data.data(), 1, (size_t)sz, f);
fclose(f);
if ((long)rd != sz) { return r; }
r.file_size = (uint64_t)sz;
/* Extract base dir */
std::string path(filename);
std::string base_dir;
size_t sep = path.find_last_of("/\\");
if (sep != std::string::npos) base_dir = path.substr(0, sep);
/* Warmup iterations (not measured) */
for (int i = 0; i < warmup; ++i) {
tg3_model model;
tg3_error_stack errors;
tg3_error_stack_init(&errors);
tg3_parse_auto(&model, &errors, data.data(), data.size(),
base_dir.c_str(), (uint32_t)base_dir.size(), NULL);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
}
/* Benchmark iterations */
std::vector<double> times;
times.reserve(iterations);
MemTracker tracker_best;
memset(&tracker_best, 0, sizeof(tracker_best));
r.rss_before = get_rss_bytes();
for (int i = 0; i < iterations; ++i) {
MemTracker tracker;
memset(&tracker, 0, sizeof(tracker));
tg3_parse_options opts;
tg3_parse_options_init(&opts);
opts.memory.allocator.alloc = tracked_alloc;
opts.memory.allocator.realloc = tracked_realloc;
opts.memory.allocator.free = tracked_free;
opts.memory.allocator.user_data = &tracker;
opts.parse_float32 = float32_mode;
tg3_model model;
tg3_error_stack errors;
tg3_error_stack_init(&errors);
TimePoint t0 = Clock::now();
tg3_error_code err = tg3_parse_auto(&model, &errors,
data.data(), data.size(),
base_dir.c_str(),
(uint32_t)base_dir.size(),
&opts);
TimePoint t1 = Clock::now();
double ms = elapsed_ms(t0, t1);
times.push_back(ms);
/* Capture model stats on first successful iteration */
if (i == 0 && err == TG3_OK) {
r.meshes = model.meshes_count;
r.nodes = model.nodes_count;
r.accessors = model.accessors_count;
r.materials = model.materials_count;
r.animations = model.animations_count;
r.buffers = model.buffers_count;
r.buffer_views = model.buffer_views_count;
r.images = model.images_count;
r.textures = model.textures_count;
}
if (tracker.peak > tracker_best.peak) {
tracker_best = tracker;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
if (err != TG3_OK && !quiet) {
fprintf(stderr, " Parse error on iteration %d: code %d\n", i, (int)err);
}
}
r.rss_after = get_rss_bytes();
r.arena_peak = tracker_best.peak;
/* Compute stats */
std::sort(times.begin(), times.end());
r.parse_min = times.front();
r.parse_max = times.back();
double sum = 0;
for (double t : times) sum += t;
r.parse_avg = sum / times.size();
r.parse_median = times[times.size() / 2];
/* Throughput: file_size / median_time */
if (r.parse_median > 0) {
r.throughput_mbs = ((double)r.file_size / (1024.0 * 1024.0)) /
(r.parse_median / 1000.0);
}
return r;
}
/* ------------------------------------------------------------------ */
/* Print results */
/* ------------------------------------------------------------------ */
static const char *human_bytes(size_t bytes, char *buf, size_t buf_sz) {
if (bytes >= 1024ULL * 1024 * 1024)
snprintf(buf, buf_sz, "%.2f GB", (double)bytes / (1024.0 * 1024 * 1024));
else if (bytes >= 1024 * 1024)
snprintf(buf, buf_sz, "%.2f MB", (double)bytes / (1024.0 * 1024));
else if (bytes >= 1024)
snprintf(buf, buf_sz, "%.2f KB", (double)bytes / 1024.0);
else
snprintf(buf, buf_sz, "%zu B", bytes);
return buf;
}
static void print_result(const BenchResult &r) {
char buf1[64], buf2[64];
printf("┌─────────────────────────────────────────────────────────────────┐\n");
printf("│ %-63s │\n", r.filename.c_str());
printf("├─────────────────────────────────────────────────────────────────┤\n");
printf("│ File size: %-47s │\n", human_bytes((size_t)r.file_size, buf1, sizeof(buf1)));
printf("│ Iterations: %-47d │\n", r.iterations);
printf("│ │\n");
printf("│ Parse time (ms): │\n");
printf("│ min: %10.3f │\n", r.parse_min);
printf("│ max: %10.3f │\n", r.parse_max);
printf("│ avg: %10.3f │\n", r.parse_avg);
printf("│ median: %10.3f │\n", r.parse_median);
printf("│ │\n");
printf("│ Throughput: %-47s │\n",
(snprintf(buf1, sizeof(buf1), "%.2f MB/s", r.throughput_mbs), buf1));
printf("│ Arena peak: %-47s │\n", human_bytes(r.arena_peak, buf1, sizeof(buf1)));
if (r.rss_before > 0) {
printf("│ RSS before: %-47s │\n", human_bytes(r.rss_before, buf1, sizeof(buf1)));
printf("│ RSS after: %-47s │\n", human_bytes(r.rss_after, buf2, sizeof(buf2)));
}
printf("│ │\n");
printf("│ Model: %u meshes, %u nodes, %u accessors, %u materials",
r.meshes, r.nodes, r.accessors, r.materials);
printf("\n");
printf("│ %u animations, %u buffers, %u images",
r.animations, r.buffers, r.images);
printf("\n");
printf("└─────────────────────────────────────────────────────────────────┘\n");
}
static void print_csv_header() {
printf("file,size_bytes,iterations,parse_min_ms,parse_max_ms,parse_avg_ms,"
"parse_median_ms,throughput_mbs,arena_peak_bytes,"
"meshes,nodes,accessors,materials,animations\n");
}
static void print_csv_row(const BenchResult &r) {
printf("%s,%lu,%d,%.3f,%.3f,%.3f,%.3f,%.2f,%zu,%u,%u,%u,%u,%u\n",
r.filename.c_str(), (unsigned long)r.file_size, r.iterations,
r.parse_min, r.parse_max, r.parse_avg, r.parse_median,
r.throughput_mbs, r.arena_peak,
r.meshes, r.nodes, r.accessors, r.materials, r.animations);
}
/* ------------------------------------------------------------------ */
/* Main */
/* ------------------------------------------------------------------ */
static void usage() {
fprintf(stderr,
"Usage:\n"
" bench_v3 <file> [--iterations N] [--warmup N] [--csv] [--quiet]\n"
" bench_v3 --batch <file1> [file2] ... [--iterations N] [--csv]\n"
"\n"
"Options:\n"
" --iterations N Number of timed parse iterations (default: 10)\n"
" --warmup N Number of warmup iterations (default: 2)\n"
" --csv Output in CSV format\n"
" --quiet Suppress per-iteration error messages\n"
" --batch Benchmark multiple files\n"
" --float32 Parse JSON floats as float32 (faster, less precise)\n");
}
int main(int argc, char **argv) {
if (argc < 2) { usage(); return 1; }
int iterations = 10;
int warmup = 2;
bool csv = false;
bool quiet = false;
int float32_mode = 0;
std::vector<std::string> files;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--iterations") == 0 && i + 1 < argc) {
iterations = atoi(argv[++i]);
} else if (strcmp(argv[i], "--warmup") == 0 && i + 1 < argc) {
warmup = atoi(argv[++i]);
} else if (strcmp(argv[i], "--csv") == 0) {
csv = true;
} else if (strcmp(argv[i], "--quiet") == 0) {
quiet = true;
} else if (strcmp(argv[i], "--float32") == 0) {
float32_mode = 1;
} else if (strcmp(argv[i], "--batch") == 0) {
/* batch mode: just collect files */
} else if (argv[i][0] != '-') {
files.push_back(argv[i]);
}
}
if (files.empty()) { usage(); return 1; }
if (csv) print_csv_header();
for (const auto &file : files) {
if (!csv && !quiet) {
printf("Benchmarking: %s (%d iterations, %d warmup%s)\n",
file.c_str(), iterations, warmup,
float32_mode ? ", float32" : "");
}
BenchResult r = bench_file(file.c_str(), iterations, warmup, quiet, float32_mode);
if (csv) {
print_csv_row(r);
} else {
print_result(r);
printf("\n");
}
}
return 0;
}

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

@@ -0,0 +1,15 @@
# -*- cmake -*-
# - Find TinyGLTF
# TinyGLTF_INCLUDE_DIR TinyGLTF's include directory
FIND_PACKAGE ( PackageHandleStandardArgs )
SET ( TinyGLTF_INCLUDE_DIR "${TinyGLTF_DIR}/../include" CACHE STRING "TinyGLTF include directory")
FIND_FILE ( TinyGLTF_HEADER tiny_gltf.h PATHS ${TinyGLTF_INCLUDE_DIR} )
IF (NOT TinyGLTF_HEADER)
MESSAGE ( FATAL_ERROR "Unable to find tiny_gltf.h, TinyGLTF_INCLUDE_DIR = ${TinyGLTF_INCLUDE_DIR}")
ENDIF ()

View File

@@ -1,3 +0,0 @@
@PACKAGE_INIT@
include(${CMAKE_CURRENT_LIST_DIR}/TinyGLTFTargets.cmake)

View File

@@ -1,6 +1,5 @@
.vs
Debug
Release
x64
packages

View File

@@ -19,7 +19,3 @@ $ make
Plese use solution file located at `basic` folder.
## Limitation
There are so many limitations in this example(e.g. no PBR shader. the shader only shows texture of textures[0] if available).

View File

@@ -22,32 +22,32 @@
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{0589AC44-0CF3-40D8-8D89-68393CFD40F3}</ProjectGuid>
<RootNamespace>basic</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>

View File

@@ -1,394 +1,365 @@
#include <fstream>
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/gtc/matrix_transform.hpp>
#include "shaders.h"
#include "window.h"
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_NOEXCEPTION
#define JSON_NOEXCEPTION
#include "../../tiny_gltf.h"
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
bool loadModel(tinygltf::Model &model, const char *filename) {
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
bool res = loader.LoadASCIIFromFile(&model, &err, &warn, filename);
if (!warn.empty()) {
std::cout << "WARN: " << warn << std::endl;
}
if (!err.empty()) {
std::cout << "ERR: " << err << std::endl;
}
if (!res)
std::cout << "Failed to load glTF: " << filename << std::endl;
else
std::cout << "Loaded glTF: " << filename << std::endl;
return res;
}
void bindMesh(std::map<int, GLuint>& vbos,
tinygltf::Model &model, tinygltf::Mesh &mesh) {
for (size_t i = 0; i < model.bufferViews.size(); ++i) {
const tinygltf::BufferView &bufferView = model.bufferViews[i];
if (bufferView.target == 0) { // TODO impl drawarrays
std::cout << "WARN: bufferView.target is zero" << std::endl;
continue; // Unsupported bufferView.
/*
From spec2.0 readme:
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
... drawArrays function should be used with a count equal to
the count property of any of the accessors referenced by the
attributes property (they are all equal for a given
primitive).
*/
}
const tinygltf::Buffer &buffer = model.buffers[bufferView.buffer];
std::cout << "bufferview.target " << bufferView.target << std::endl;
GLuint vbo;
glGenBuffers(1, &vbo);
vbos[i] = vbo;
glBindBuffer(bufferView.target, vbo);
std::cout << "buffer.data.size = " << buffer.data.size()
<< ", bufferview.byteOffset = " << bufferView.byteOffset
<< std::endl;
glBufferData(bufferView.target, bufferView.byteLength,
&buffer.data.at(0) + bufferView.byteOffset, GL_STATIC_DRAW);
}
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
tinygltf::Primitive primitive = mesh.primitives[i];
tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
for (auto &attrib : primitive.attributes) {
tinygltf::Accessor accessor = model.accessors[attrib.second];
int byteStride =
accessor.ByteStride(model.bufferViews[accessor.bufferView]);
glBindBuffer(GL_ARRAY_BUFFER, vbos[accessor.bufferView]);
int size = 1;
if (accessor.type != TINYGLTF_TYPE_SCALAR) {
size = accessor.type;
}
int vaa = -1;
if (attrib.first.compare("POSITION") == 0) vaa = 0;
if (attrib.first.compare("NORMAL") == 0) vaa = 1;
if (attrib.first.compare("TEXCOORD_0") == 0) vaa = 2;
if (vaa > -1) {
glEnableVertexAttribArray(vaa);
glVertexAttribPointer(vaa, size, accessor.componentType,
accessor.normalized ? GL_TRUE : GL_FALSE,
byteStride, BUFFER_OFFSET(accessor.byteOffset));
} else
std::cout << "vaa missing: " << attrib.first << std::endl;
}
if (model.textures.size() > 0) {
// fixme: Use material's baseColor
tinygltf::Texture &tex = model.textures[0];
if (tex.source > -1) {
GLuint texid;
glGenTextures(1, &texid);
tinygltf::Image &image = model.images[tex.source];
glBindTexture(GL_TEXTURE_2D, texid);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
GLenum format = GL_RGBA;
if (image.component == 1) {
format = GL_RED;
} else if (image.component == 2) {
format = GL_RG;
} else if (image.component == 3) {
format = GL_RGB;
} else {
// ???
}
GLenum type = GL_UNSIGNED_BYTE;
if (image.bits == 8) {
// ok
} else if (image.bits == 16) {
type = GL_UNSIGNED_SHORT;
} else {
// ???
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0,
format, type, &image.image.at(0));
}
}
}
}
// bind models
void bindModelNodes(std::map<int, GLuint>& vbos, tinygltf::Model &model,
tinygltf::Node &node) {
if ((node.mesh >= 0) && (node.mesh < model.meshes.size())) {
bindMesh(vbos, model, model.meshes[node.mesh]);
}
for (size_t i = 0; i < node.children.size(); i++) {
assert((node.children[i] >= 0) && (node.children[i] < model.nodes.size()));
bindModelNodes(vbos, model, model.nodes[node.children[i]]);
}
}
std::pair<GLuint, std::map<int, GLuint>> bindModel(tinygltf::Model &model) {
std::map<int, GLuint> vbos;
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
const tinygltf::Scene &scene = model.scenes[model.defaultScene];
for (size_t i = 0; i < scene.nodes.size(); ++i) {
assert((scene.nodes[i] >= 0) && (scene.nodes[i] < model.nodes.size()));
bindModelNodes(vbos, model, model.nodes[scene.nodes[i]]);
}
glBindVertexArray(0);
// cleanup vbos but do not delete index buffers yet
for (auto it = vbos.cbegin(); it != vbos.cend();) {
tinygltf::BufferView bufferView = model.bufferViews[it->first];
if (bufferView.target != GL_ELEMENT_ARRAY_BUFFER) {
glDeleteBuffers(1, &vbos[it->first]);
vbos.erase(it++);
}
else {
++it;
}
}
return {vao, vbos};
}
void drawMesh(const std::map<int, GLuint>& vbos,
tinygltf::Model &model, tinygltf::Mesh &mesh) {
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
tinygltf::Primitive primitive = mesh.primitives[i];
tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos.at(indexAccessor.bufferView));
glDrawElements(primitive.mode, indexAccessor.count,
indexAccessor.componentType,
BUFFER_OFFSET(indexAccessor.byteOffset));
}
}
// recursively draw node and children nodes of model
void drawModelNodes(const std::pair<GLuint, std::map<int, GLuint>>& vaoAndEbos,
tinygltf::Model &model, tinygltf::Node &node) {
if ((node.mesh >= 0) && (node.mesh < model.meshes.size())) {
drawMesh(vaoAndEbos.second, model, model.meshes[node.mesh]);
}
for (size_t i = 0; i < node.children.size(); i++) {
drawModelNodes(vaoAndEbos, model, model.nodes[node.children[i]]);
}
}
void drawModel(const std::pair<GLuint, std::map<int, GLuint>>& vaoAndEbos,
tinygltf::Model &model) {
glBindVertexArray(vaoAndEbos.first);
const tinygltf::Scene &scene = model.scenes[model.defaultScene];
for (size_t i = 0; i < scene.nodes.size(); ++i) {
drawModelNodes(vaoAndEbos, model, model.nodes[scene.nodes[i]]);
}
glBindVertexArray(0);
}
void dbgModel(tinygltf::Model &model) {
for (auto &mesh : model.meshes) {
std::cout << "mesh : " << mesh.name << std::endl;
for (auto &primitive : mesh.primitives) {
const tinygltf::Accessor &indexAccessor =
model.accessors[primitive.indices];
std::cout << "indexaccessor: count " << indexAccessor.count << ", type "
<< indexAccessor.componentType << std::endl;
tinygltf::Material &mat = model.materials[primitive.material];
for (auto &mats : mat.values) {
std::cout << "mat : " << mats.first.c_str() << std::endl;
}
for (auto &image : model.images) {
std::cout << "image name : " << image.uri << std::endl;
std::cout << " size : " << image.image.size() << std::endl;
std::cout << " w/h : " << image.width << "/" << image.height
<< std::endl;
}
std::cout << "indices : " << primitive.indices << std::endl;
std::cout << "mode : "
<< "(" << primitive.mode << ")" << std::endl;
for (auto &attrib : primitive.attributes) {
std::cout << "attribute : " << attrib.first.c_str() << std::endl;
}
}
}
}
glm::mat4 genView(glm::vec3 pos, glm::vec3 lookat) {
// Camera matrix
glm::mat4 view = glm::lookAt(
pos, // Camera in World Space
lookat, // and looks at the origin
glm::vec3(0, 1, 0) // Head is up (set to 0,-1,0 to look upside-down)
);
return view;
}
glm::mat4 genMVP(glm::mat4 view_mat, glm::mat4 model_mat, float fov, int w,
int h) {
glm::mat4 Projection =
glm::perspective(glm::radians(fov), (float)w / (float)h, 0.01f, 1000.0f);
// Or, for an ortho camera :
// glm::mat4 Projection = glm::ortho(-10.0f,10.0f,-10.0f,10.0f,0.0f,100.0f);
// // In world coordinates
glm::mat4 mvp = Projection * view_mat * model_mat;
return mvp;
}
void displayLoop(Window &window, const std::string &filename) {
Shaders shader = Shaders();
glUseProgram(shader.pid);
// grab uniforms to modify
GLuint MVP_u = glGetUniformLocation(shader.pid, "MVP");
GLuint sun_position_u = glGetUniformLocation(shader.pid, "sun_position");
GLuint sun_color_u = glGetUniformLocation(shader.pid, "sun_color");
tinygltf::Model model;
if (!loadModel(model, filename.c_str())) return;
std::pair<GLuint, std::map<int, GLuint>> vaoAndEbos = bindModel(model);
// dbgModel(model); return;
// Model matrix : an identity matrix (model will be at the origin)
glm::mat4 model_mat = glm::mat4(1.0f);
glm::mat4 model_rot = glm::mat4(1.0f);
glm::vec3 model_pos = glm::vec3(-3, 0, -3);
// generate a camera view, based on eye-position and lookAt world-position
glm::mat4 view_mat = genView(glm::vec3(2, 2, 20), model_pos);
glm::vec3 sun_position = glm::vec3(3.0, 10.0, -5.0);
glm::vec3 sun_color = glm::vec3(1.0);
while (!window.Close()) {
window.Resize();
glClearColor(0.2, 0.2, 0.2, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 trans =
glm::translate(glm::mat4(1.0f), model_pos); // reposition model
model_rot = glm::rotate(model_rot, glm::radians(0.8f),
glm::vec3(0, 1, 0)); // rotate model on y axis
model_mat = trans * model_rot;
// build a model-view-projection
GLint w, h;
glfwGetWindowSize(window.window, &w, &h);
glm::mat4 mvp = genMVP(view_mat, model_mat, 45.0f, w, h);
glUniformMatrix4fv(MVP_u, 1, GL_FALSE, &mvp[0][0]);
glUniform3fv(sun_position_u, 1, &sun_position[0]);
glUniform3fv(sun_color_u, 1, &sun_color[0]);
drawModel(vaoAndEbos, model);
glfwSwapBuffers(window.window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &vaoAndEbos.first);
}
static void error_callback(int error, const char *description) {
(void)error;
fprintf(stderr, "Error: %s\n", description);
}
int main(int argc, char **argv) {
std::string filename = "../../../models/Cube/Cube.gltf";
if (argc > 1) {
filename = argv[1];
}
glfwSetErrorCallback(error_callback);
if (!glfwInit()) return -1;
// Force create OpenGL 3.3
// NOTE(syoyo): Linux + NVIDIA driver segfaults for some reason? commenting out glfwWindowHint will work.
// Note (PE): On laptops with intel hd graphics card you can overcome the segfault by enabling experimental, see below (tested on lenovo thinkpad)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glewExperimental = GL_TRUE;
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
Window window = Window(800, 600, "TinyGLTF basic example");
glfwMakeContextCurrent(window.window);
#ifdef __APPLE__
// https://stackoverflow.com/questions/50192625/openggl-segmentation-fault
glewExperimental = GL_TRUE;
#endif
glewInit();
std::cout << glGetString(GL_RENDERER) << ", " << glGetString(GL_VERSION)
<< std::endl;
if (!GLEW_VERSION_3_3) {
std::cerr << "OpenGL 3.3 is required to execute this app." << std::endl;
return EXIT_FAILURE;
}
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
displayLoop(window, filename);
glfwTerminate();
return 0;
}
#include <fstream>
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/gtc/matrix_transform.hpp>
#include "shaders.h"
#include "window.h"
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_NOEXCEPTION
#define JSON_NOEXCEPTION
#include "../../tiny_gltf.h"
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
bool loadModel(tinygltf::Model &model, const char *filename) {
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
bool res = loader.LoadASCIIFromFile(&model, &err, &warn, filename);
if (!warn.empty()) {
std::cout << "WARN: " << warn << std::endl;
}
if (!err.empty()) {
std::cout << "ERR: " << err << std::endl;
}
if (!res)
std::cout << "Failed to load glTF: " << filename << std::endl;
else
std::cout << "Loaded glTF: " << filename << std::endl;
return res;
}
std::map<int, GLuint> bindMesh(std::map<int, GLuint> vbos,
tinygltf::Model &model, tinygltf::Mesh &mesh) {
for (size_t i = 0; i < model.bufferViews.size(); ++i) {
const tinygltf::BufferView &bufferView = model.bufferViews[i];
if (bufferView.target == 0) { // TODO impl drawarrays
std::cout << "WARN: bufferView.target is zero" << std::endl;
continue; // Unsupported bufferView.
/*
From spec2.0 readme:
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
... drawArrays function should be used with a count equal to
the count property of any of the accessors referenced by the
attributes property (they are all equal for a given
primitive).
*/
}
tinygltf::Buffer buffer = model.buffers[bufferView.buffer];
std::cout << "bufferview.target " << bufferView.target << std::endl;
GLuint vbo;
glGenBuffers(1, &vbo);
vbos[i] = vbo;
glBindBuffer(bufferView.target, vbo);
std::cout << "buffer.data.size = " << buffer.data.size()
<< ", bufferview.byteOffset = " << bufferView.byteOffset
<< std::endl;
glBufferData(bufferView.target, bufferView.byteLength,
&buffer.data.at(0) + bufferView.byteOffset, GL_STATIC_DRAW);
}
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
tinygltf::Primitive primitive = mesh.primitives[i];
tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
for (auto &attrib : primitive.attributes) {
tinygltf::Accessor accessor = model.accessors[attrib.second];
int byteStride =
accessor.ByteStride(model.bufferViews[accessor.bufferView]);
glBindBuffer(GL_ARRAY_BUFFER, vbos[accessor.bufferView]);
int size = 1;
if (accessor.type != TINYGLTF_TYPE_SCALAR) {
size = accessor.type;
}
int vaa = -1;
if (attrib.first.compare("POSITION") == 0) vaa = 0;
if (attrib.first.compare("NORMAL") == 0) vaa = 1;
if (attrib.first.compare("TEXCOORD_0") == 0) vaa = 2;
if (vaa > -1) {
glEnableVertexAttribArray(vaa);
glVertexAttribPointer(vaa, size, accessor.componentType,
accessor.normalized ? GL_TRUE : GL_FALSE,
byteStride, BUFFER_OFFSET(accessor.byteOffset));
} else
std::cout << "vaa missing: " << attrib.first << std::endl;
}
GLuint texid;
glGenTextures(1, &texid);
tinygltf::Texture &tex = model.textures[0];
tinygltf::Image &image = model.images[tex.source];
glBindTexture(GL_TEXTURE_2D, texid);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
GLenum format = GL_RGBA;
if (image.component == 1) {
format = GL_RED;
} else if (image.component == 2) {
format = GL_RG;
} else if (image.component == 3) {
format = GL_RGB;
} else {
// ???
}
GLenum type = GL_UNSIGNED_BYTE;
if (image.bits == 8) {
// ok
} else if (image.bits == 16) {
type = GL_UNSIGNED_SHORT;
} else {
// ???
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width, image.height, 0,
format, type, &image.image.at(0));
}
return vbos;
}
// bind models
void bindModelNodes(std::map<int, GLuint> vbos, tinygltf::Model &model,
tinygltf::Node &node) {
bindMesh(vbos, model, model.meshes[node.mesh]);
for (size_t i = 0; i < node.children.size(); i++) {
bindModelNodes(vbos, model, model.nodes[node.children[i]]);
}
}
GLuint bindModel(tinygltf::Model &model) {
std::map<int, GLuint> vbos;
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
const tinygltf::Scene &scene = model.scenes[model.defaultScene];
for (size_t i = 0; i < scene.nodes.size(); ++i) {
bindModelNodes(vbos, model, model.nodes[scene.nodes[i]]);
}
glBindVertexArray(0);
// cleanup vbos
for (size_t i = 0; i < vbos.size(); ++i) {
glDeleteBuffers(1, &vbos[i]);
}
return vao;
}
void drawMesh(tinygltf::Model &model, tinygltf::Mesh &mesh) {
for (size_t i = 0; i < mesh.primitives.size(); ++i) {
tinygltf::Primitive primitive = mesh.primitives[i];
tinygltf::Accessor indexAccessor = model.accessors[primitive.indices];
glDrawElements(primitive.mode, indexAccessor.count,
indexAccessor.componentType,
BUFFER_OFFSET(indexAccessor.byteOffset));
}
}
// recursively draw node and children nodes of model
void drawModelNodes(tinygltf::Model &model, tinygltf::Node &node) {
drawMesh(model, model.meshes[node.mesh]);
for (size_t i = 0; i < node.children.size(); i++) {
drawModelNodes(model, model.nodes[node.children[i]]);
}
}
void drawModel(GLuint vao, tinygltf::Model &model) {
glBindVertexArray(vao);
const tinygltf::Scene &scene = model.scenes[model.defaultScene];
for (size_t i = 0; i < scene.nodes.size(); ++i) {
drawModelNodes(model, model.nodes[scene.nodes[i]]);
}
glBindVertexArray(0);
}
void dbgModel(tinygltf::Model &model) {
for (auto &mesh : model.meshes) {
std::cout << "mesh : " << mesh.name << std::endl;
for (auto &primitive : mesh.primitives) {
const tinygltf::Accessor &indexAccessor =
model.accessors[primitive.indices];
std::cout << "indexaccessor: count " << indexAccessor.count << ", type "
<< indexAccessor.componentType << std::endl;
tinygltf::Material &mat = model.materials[primitive.material];
for (auto &mats : mat.values) {
std::cout << "mat : " << mats.first.c_str() << std::endl;
}
for (auto &image : model.images) {
std::cout << "image name : " << image.uri << std::endl;
std::cout << " size : " << image.image.size() << std::endl;
std::cout << " w/h : " << image.width << "/" << image.height
<< std::endl;
}
std::cout << "indices : " << primitive.indices << std::endl;
std::cout << "mode : "
<< "(" << primitive.mode << ")" << std::endl;
for (auto &attrib : primitive.attributes) {
std::cout << "attribute : " << attrib.first.c_str() << std::endl;
}
}
}
}
glm::mat4 genView(glm::vec3 pos, glm::vec3 lookat) {
// Camera matrix
glm::mat4 view = glm::lookAt(
pos, // Camera in World Space
lookat, // and looks at the origin
glm::vec3(0, 1, 0) // Head is up (set to 0,-1,0 to look upside-down)
);
return view;
}
glm::mat4 genMVP(glm::mat4 view_mat, glm::mat4 model_mat, float fov, int w,
int h) {
glm::mat4 Projection =
glm::perspective(glm::radians(fov), (float)w / (float)h, 0.01f, 1000.0f);
// Or, for an ortho camera :
// glm::mat4 Projection = glm::ortho(-10.0f,10.0f,-10.0f,10.0f,0.0f,100.0f);
// // In world coordinates
glm::mat4 mvp = Projection * view_mat * model_mat;
return mvp;
}
void displayLoop(Window &window, const std::string &filename) {
Shaders shader = Shaders();
glUseProgram(shader.pid);
// grab uniforms to modify
GLuint MVP_u = glGetUniformLocation(shader.pid, "MVP");
GLuint sun_position_u = glGetUniformLocation(shader.pid, "sun_position");
GLuint sun_color_u = glGetUniformLocation(shader.pid, "sun_color");
tinygltf::Model model;
if (!loadModel(model, filename.c_str())) return;
GLuint vao = bindModel(model);
// dbgModel(model); return;
// Model matrix : an identity matrix (model will be at the origin)
glm::mat4 model_mat = glm::mat4(1.0f);
glm::mat4 model_rot = glm::mat4(1.0f);
glm::vec3 model_pos = glm::vec3(-3, 0, -3);
// generate a camera view, based on eye-position and lookAt world-position
glm::mat4 view_mat = genView(glm::vec3(2, 2, 20), model_pos);
glm::vec3 sun_position = glm::vec3(3.0, 10.0, -5.0);
glm::vec3 sun_color = glm::vec3(1.0);
while (!window.Close()) {
window.Resize();
glClearColor(0.2, 0.2, 0.2, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 trans =
glm::translate(glm::mat4(1.0f), model_pos); // reposition model
model_rot = glm::rotate(model_rot, glm::radians(0.8f),
glm::vec3(0, 1, 0)); // rotate model on y axis
model_mat = trans * model_rot;
// build a model-view-projection
GLint w, h;
glfwGetWindowSize(window.window, &w, &h);
glm::mat4 mvp = genMVP(view_mat, model_mat, 45.0f, w, h);
glUniformMatrix4fv(MVP_u, 1, GL_FALSE, &mvp[0][0]);
glUniform3fv(sun_position_u, 1, &sun_position[0]);
glUniform3fv(sun_color_u, 1, &sun_color[0]);
drawModel(vao, model);
glfwSwapBuffers(window.window);
glfwPollEvents();
}
}
static void error_callback(int error, const char *description) {
(void)error;
fprintf(stderr, "Error: %s\n", description);
}
int main(int argc, char **argv) {
std::string filename = "../../models/Cube/Cube.gltf";
if (argc > 1) {
filename = argv[1];
}
glfwSetErrorCallback(error_callback);
if (!glfwInit()) return -1;
// Force create OpenGL 3.3
// NOTE(syoyo): Linux + NVIDIA driver segfaults for some reason? commenting out glfwWindowHint will work.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
#endif
Window window = Window(800, 600, "TinyGLTF basic example");
glfwMakeContextCurrent(window.window);
#ifdef __APPLE__
// https://stackoverflow.com/questions/50192625/openggl-segmentation-fault
glewExperimental = GL_TRUE;
#endif
glewInit();
std::cout << glGetString(GL_RENDERER) << ", " << glGetString(GL_VERSION)
<< std::endl;
if (!GLEW_VERSION_3_3) {
std::cerr << "OpenGL 3.3 is required to execute this app." << std::endl;
return EXIT_FAILURE;
}
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
displayLoop(window, filename);
glfwTerminate();
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

@@ -1,2 +0,0 @@
all:
$(CXX) -o create_triangle_gltf -I../../ create_triangle_gltf.cpp

View File

@@ -1,121 +0,0 @@
// An example of how to generate a gltf file from scratch. This example
// was translated from the pygltlib documentation in the pypi project page,
// which in turn is based on the Khronos Sample Models at:
//
// https://github.com/KhronosGroup/glTF-Sample-Models
//
// This example is released under the MIT license.
//
// 2021-02-25 Thu
// Dov Grobgeld <dov.grobgeld@gmail.com>
// Define these only in *one* .cc file.
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
// #define TINYGLTF_NOEXCEPTION // optional. disable exception handling.
#include "tiny_gltf.h"
int main(int argc, char **argv)
{
// Create a model with a single mesh and save it as a gltf file
tinygltf::Model m;
tinygltf::Scene scene;
tinygltf::Mesh mesh;
tinygltf::Primitive primitive;
tinygltf::Node node;
tinygltf::Buffer buffer;
tinygltf::BufferView bufferView1;
tinygltf::BufferView bufferView2;
tinygltf::Accessor accessor1;
tinygltf::Accessor accessor2;
tinygltf::Asset asset;
// This is the raw data buffer.
buffer.data = {
// 6 bytes of indices and two bytes of padding
0x00,0x00,0x01,0x00,0x02,0x00,0x00,0x00,
// 36 bytes of floating point numbers
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3f,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x3f,
0x00,0x00,0x00,0x00};
// "The indices of the vertices (ELEMENT_ARRAY_BUFFER) take up 6 bytes in the
// start of the buffer.
bufferView1.buffer = 0;
bufferView1.byteOffset=0;
bufferView1.byteLength=6;
bufferView1.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
// The vertices take up 36 bytes (3 vertices * 3 floating points * 4 bytes)
// at position 8 in the buffer and are of type ARRAY_BUFFER
bufferView2.buffer = 0;
bufferView2.byteOffset=8;
bufferView2.byteLength=36;
bufferView2.target = TINYGLTF_TARGET_ARRAY_BUFFER;
// Describe the layout of bufferView1, the indices of the vertices
accessor1.bufferView = 0;
accessor1.byteOffset = 0;
accessor1.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT;
accessor1.count = 3;
accessor1.type = TINYGLTF_TYPE_SCALAR;
accessor1.maxValues.push_back(2);
accessor1.minValues.push_back(0);
// Describe the layout of bufferView2, the vertices themself
accessor2.bufferView = 1;
accessor2.byteOffset = 0;
accessor2.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
accessor2.count = 3;
accessor2.type = TINYGLTF_TYPE_VEC3;
accessor2.maxValues = {1.0, 1.0, 0.0};
accessor2.minValues = {0.0, 0.0, 0.0};
// Build the mesh primitive and add it to the mesh
primitive.indices = 0; // The index of the accessor for the vertex indices
primitive.attributes["POSITION"] = 1; // The index of the accessor for positions
primitive.material = 0;
primitive.mode = TINYGLTF_MODE_TRIANGLES;
mesh.primitives.push_back(primitive);
// Other tie ups
node.mesh = 0;
scene.nodes.push_back(0); // Default scene
// Define the asset. The version is required
asset.version = "2.0";
asset.generator = "tinygltf";
// Now all that remains is to tie back all the loose objects into the
// our single model.
m.scenes.push_back(scene);
m.meshes.push_back(mesh);
m.nodes.push_back(node);
m.buffers.push_back(buffer);
m.bufferViews.push_back(bufferView1);
m.bufferViews.push_back(bufferView2);
m.accessors.push_back(accessor1);
m.accessors.push_back(accessor2);
m.asset = asset;
// Create a simple material
tinygltf::Material mat;
mat.pbrMetallicRoughness.baseColorFactor = {1.0f, 0.9f, 0.9f, 1.0f};
mat.doubleSided = true;
m.materials.push_back(mat);
// Save it to a file
tinygltf::TinyGLTF gltf;
gltf.WriteGltfSceneToFile(&m, "triangle.gltf",
true, // embedImages
true, // embedBuffers
true, // pretty print
false); // write binary
exit(0);
}

View File

@@ -1,36 +0,0 @@
cmake_minimum_required(VERSION 3.5)
project(dxview)
find_package(glfw3 CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)
add_executable(dxview
src/Viewer.h
src/Viewer.cc
src/dxview.cc
)
target_include_directories(dxview
PRIVATE
../../
)
target_compile_definitions(dxview
PRIVATE
DXVIEW_SWAP_CHAIN_BUFFER_COUNT=3
DXVIEW_RES_DIR=L"${PROJECT_SOURCE_DIR}/res"
)
target_link_libraries(dxview
PRIVATE
dxgi
d3dcompiler
d3d12
glfw
spdlog::spdlog
)
set_target_properties(dxview
PROPERTIES
CXX_STANDARD 17
)

View File

@@ -1,37 +0,0 @@
# DirectX glTF Viewer
## Overview
This project was motivated by a lack of sample code demonstrating the graphics API agnostic nature of the glTF specification. The sample code is written using modern C++ and DirectX 12 for the client application.
## Features
* [x] DirectX 12
* [ ] Loader
* [ ] Animation
* [ ] Morph Target
* [ ] Physical Base Rendering
* [ ] Environment Map
## Dependencies
* [CMake](https://github.com/Kitware/CMake)
* [Vcpkg](https://github.com/Microsoft/vcpkg)
* [GLFW](https://github.com/glfw/glfw)
* [spdlog](https://github.com/gabime/spdlog)
## Building
### Install dependencies
```
vcpkg install glfw3:x64-windows
vcpkg install spdlog:x64-windows
```
### Generate Project Files
```
mkdir build
cmake . -B build -DCMAKE_TOOLCHAIN_FILE=${VCPKG_DIR}/script/buildsystem/vcpkg.cmake
```

View File

@@ -1,3 +0,0 @@
float4 main() : SV_Target {
return float4(0.5, 0.5, 0.5, 1.0);
}

View File

@@ -1,52 +0,0 @@
struct RS2PS {
float4 position : SV_POSITION;
#ifdef HAS_TEXCOORD_0
float2 texcoord_0: TEXCOORD_0;
#endif
};
struct TextureInfo {
uint textureIndex;
uint samplerIndex;
};
struct PBRMetallicRoughness {
float4 baseColorFactor;
TextureInfo baseColorTexture;
float metallicFactor;
float roughnessFactor;
TextureInfo metallicRoughnessTexture;
};
cbuffer Material : register(b2) {
PBRMetallicRoughness pbrMetallicRoughness;
};
Texture2D textures[5] : register(t0);
SamplerState samplerState[5] : register(s0);
float4 getBaseColor(float2 uv) {
float4 baseColor = pbrMetallicRoughness.baseColorFactor;
#ifdef HAS_TEXCOORD_0
TextureInfo baseColorTexture = pbrMetallicRoughness.baseColorTexture;
if (baseColorTexture.textureIndex >= 0) {
baseColor *= textures[baseColorTexture.textureIndex].Sample(samplerState[baseColorTexture.samplerIndex], uv);
}
#endif
return baseColor;
}
float4 main(RS2PS input) : SV_Target {
float2 uv = float2(0.0, 0.0);
#ifdef HAS_TEXCOORD_0
uv = input.texcoord_0;
#endif
float4 color = getBaseColor(uv);
return color;
}

View File

@@ -1,46 +0,0 @@
struct IA2VS {
float3 position : POSITION;
#ifdef HAS_NORMAL
float3 normal : NORMAL;
#endif
#ifdef HAS_TANGENT
float4 tangent : TANGENT;
#endif
#ifdef HAS_TEXCOORD_0
float2 texcoord_0: TEXCOORD_0;
#endif
};
struct VS2RS {
float4 position : SV_POSITION;
#ifdef HAS_TEXCOORD_0
float2 texcoord_0: TEXCOORD_0;
#endif
};
cbuffer Camera : register(b0) {
float4x4 V;
float4x4 P;
float4x4 VP;
};
cbuffer Node : register(b1) {
float4x4 M;
};
VS2RS main(IA2VS input) {
VS2RS output;
output.position = mul(float4(input.position, 1.0), M);
output.position = mul(output.position, V);
output.position = mul(output.position, P);
#ifdef HAS_TEXCOORD_0
output.texcoord_0 = input.texcoord_0;
#endif
return output;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,130 +0,0 @@
#ifndef DXVIEW_VIEWER_GUARD
#define DXVIEW_VIEWER_GUARD
#include <DirectXMath.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <tiny_gltf.h>
#include <wrl.h>
#include <filesystem>
#include <map>
#include <string>
#include <vector>
using Microsoft::WRL::ComPtr;
enum RenderPassType { RENDER_PASS_TYPE_PRESENT = 0, RENDER_PASS_TYPE_COUNT };
struct RenderTarget {
ComPtr<ID3D12Resource> pTexture;
D3D12_CPU_DESCRIPTOR_HANDLE viewDescriptor;
};
struct TextureInfo {
int32_t textureIndex;
int32_t samplerIndex;
};
struct PBRMetallicRoughness {
DirectX::XMFLOAT4 baseColorFactor;
TextureInfo baseColorTexture;
float metallicFactor;
float roughnessFactor;
TextureInfo metallicRoughnessTexture;
};
struct Material {
std::string name;
D3D12_BLEND_DESC blendDesc;
D3D12_RASTERIZER_DESC rasterizerDesc;
ComPtr<ID3D12Resource> pBuffer;
void* pBufferData;
ComPtr<ID3D12DescriptorHeap> pSRVDescriptorHeap;
ComPtr<ID3D12DescriptorHeap> pSamplerDescriptorHeap;
};
struct Attribute {
std::string name;
DXGI_FORMAT format;
D3D12_VERTEX_BUFFER_VIEW vertexBufferView;
};
struct Primitive {
std::vector<Attribute> attributes;
uint32_t vertexCount;
D3D12_PRIMITIVE_TOPOLOGY primitiveTopology;
D3D12_INDEX_BUFFER_VIEW indexBufferView;
uint32_t indexCount;
Material* pMaterial;
ComPtr<ID3D12RootSignature> pRootSignature;
ComPtr<ID3D12PipelineState> pPipelineState;
};
struct Mesh {
std::string name;
std::vector<Primitive> primitives;
};
struct Node {
DirectX::XMFLOAT4X4 M;
};
struct Camera {
DirectX::XMFLOAT4X4 V;
DirectX::XMFLOAT4X4 P;
DirectX::XMFLOAT4X4 VP;
};
class Viewer {
public:
Viewer(HWND window, tinygltf::Model* pModel);
void update(double deltaTime);
void render(double deltaTime);
private:
void initDirectX(HWND window);
void buildRenderTargets();
void buildResources();
void buildBuffers(std::vector<ComPtr<ID3D12Resource> >* pStagingResources);
void buildImages(std::vector<ComPtr<ID3D12Resource> >* pStagingResources);
void buildSamplerDescs();
void buildMaterials();
void buildMeshes();
void buildNodes();
void drawNode(uint64_t nodeIndex);
private:
tinygltf::Model* pModel_;
ComPtr<IDXGIFactory1> pFactory_;
ComPtr<IDXGIAdapter1> pAdapter_;
ComPtr<ID3D12Device> pDevice_;
ComPtr<ID3D12CommandQueue> pDirectCommandQueue_;
UINT64 directFenceValue_;
ComPtr<ID3D12Fence> pDirectFence_;
ComPtr<ID3D12CommandQueue> pCopyCommandQueue_;
UINT64 copyFenceValue_;
ComPtr<ID3D12Fence> pCopyFence_;
ComPtr<IDXGISwapChain3> pSwapChain_;
ComPtr<ID3D12Resource> pSwapChainBuffers_[DXVIEW_SWAP_CHAIN_BUFFER_COUNT];
ComPtr<ID3D12CommandAllocator>
pDirectCommandAllocators_[DXVIEW_SWAP_CHAIN_BUFFER_COUNT];
ComPtr<ID3D12GraphicsCommandList> pDirectCommandList_;
ComPtr<ID3D12CommandAllocator> pCopyCommandAllocator_;
ComPtr<ID3D12GraphicsCommandList> pCopyCommandList_;
UINT descriptorIncrementSize_[D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES];
ComPtr<ID3D12DescriptorHeap>
pRTVDescriptorHeaps_[DXVIEW_SWAP_CHAIN_BUFFER_COUNT];
std::vector<RenderTarget> renderTargets_[DXVIEW_SWAP_CHAIN_BUFFER_COUNT];
std::vector<ComPtr<ID3D12Resource> > pBuffers_;
std::vector<ComPtr<ID3D12Resource> > pTextures_;
std::vector<D3D12_SAMPLER_DESC> samplerDescs_;
std::vector<Material> materials_;
std::vector<Mesh> meshes_;
std::vector<ComPtr<ID3D12Resource> > pNodeBuffers_;
ComPtr<ID3D12Resource> pCameraBuffer_;
};
#endif

View File

@@ -1,90 +0,0 @@
#define GLFW_EXPOSE_NATIVE_WIN32
#define TINYGLTF_IMPLEMENTATION
#define STBI_MSC_SECURE_CRT
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <spdlog/spdlog.h>
#include <tiny_gltf.h>
#undef GLFW_EXPOSE_NATIVE_WIN32
#undef TINYGLTF_IMPLEMENTATION
#undef STBI_MSC_SECURE_CRT
#undef STB_IMAGE_IMPLEMENTATION
#undef STB_IMAGE_WRITE_IMPLEMENTATION
#include <iostream>
#include <string>
#include "Viewer.h"
static void onError(int error, const char* message) {
spdlog::error("[{}] {}", error, message);
}
static void onRender(Viewer* pViewer, double deltaTime) {
pViewer->update(deltaTime);
pViewer->render(deltaTime);
}
int main(int argc, char* argv[]) {
tinygltf::TinyGLTF context;
tinygltf::Model model;
std::string error;
std::string warning;
if (!context.LoadASCIIFromFile(&model, &error, &warning, argv[1])) {
if (!error.empty()) {
spdlog::error("{}", error);
}
if (!warning.empty()) {
spdlog::warn("{}", warning);
}
exit(EXIT_FAILURE);
}
GLFWwindow* pWindow;
glfwSetErrorCallback(onError);
if (!glfwInit()) {
spdlog::error("Fail to initialize GLFW!!!");
exit(EXIT_FAILURE);
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
pWindow = glfwCreateWindow(512, 512, "dxview", nullptr, nullptr);
if (!pWindow) {
spdlog::error("Fail to create GLFWwindow!!!");
glfwTerminate();
exit(EXIT_FAILURE);
}
auto pViewer = std::make_unique<Viewer>(glfwGetWin32Window(pWindow), &model);
if (!pViewer) {
spdlog::error("Fail to create Viewer");
glfwDestroyWindow(pWindow);
glfwTerminate();
exit(EXIT_FAILURE);
}
auto prevTimeStamp = glfwGetTime();
while (!glfwWindowShouldClose(pWindow)) {
auto currTimeStamp = glfwGetTime();
onRender(pViewer.get(), currTimeStamp - prevTimeStamp);
prevTimeStamp = currTimeStamp;
glfwPollEvents();
}
glfwDestroyWindow(pWindow);
glfwTerminate();
exit(EXIT_SUCCESS);
}

View File

@@ -29,7 +29,7 @@ if (DEFINED DRACO_DIR)
# TODO(syoyo): better CMake script for draco
add_definitions(-DTINYGLTF_ENABLE_DRACO)
include_directories(${DRACO_DIR}/include)
link_directories(${DRACO_DIR}/lib)
set(DRACO_LIBRARY draco)
endif ()
@@ -49,9 +49,9 @@ add_executable(glview
)
target_link_libraries ( glview
${DRACO_LIBRARY}
${DRACO_LIBRARY}
${GLFW3_UNIX_LINK_LIBRARIES}
${GLEW_LIBRARIES}
${GLEW_LIBRARY}
${GLFW3_glfw_LIBRARY}
${OPENGL_gl_LIBRARY}
${OPENGL_glu_LIBRARY}

View File

@@ -771,32 +771,6 @@ static void DrawCurves(tinygltf::Scene &scene, const tinygltf::Mesh &mesh) {
}
#endif
static void QuatToAngleAxis(const std::vector<double> quaternion,
double &outAngleDegrees,
double *axis) {
double qx = quaternion[0];
double qy = quaternion[1];
double qz = quaternion[2];
double qw = quaternion[3];
double angleRadians = 2 * acos(qw);
if (angleRadians == 0.0) {
outAngleDegrees = 0.0;
axis[0] = 0.0;
axis[1] = 0.0;
axis[2] = 1.0;
return;
}
constexpr double pi = 3.14159265358979323846;
double denom = sqrt(1-qw*qw);
outAngleDegrees = angleRadians * 180.0 / pi;
axis[0] = qx / denom;
axis[1] = qy / denom;
axis[2] = qz / denom;
}
// Hierarchically draw nodes
static void DrawNode(tinygltf::Model &model, const tinygltf::Node &node) {
// Apply xform
@@ -807,22 +781,18 @@ static void DrawNode(tinygltf::Model &model, const tinygltf::Node &node) {
glMultMatrixd(node.matrix.data());
} else {
// Assume Trans x Rotate x Scale order
if (node.scale.size() == 3) {
glScaled(node.scale[0], node.scale[1], node.scale[2]);
}
if (node.rotation.size() == 4) {
glRotated(node.rotation[0], node.rotation[1], node.rotation[2],
node.rotation[3]);
}
if (node.translation.size() == 3) {
glTranslated(node.translation[0], node.translation[1],
node.translation[2]);
}
if (node.rotation.size() == 4) {
double angleDegrees;
double axis[3];
QuatToAngleAxis(node.rotation, angleDegrees, axis);
glRotated(angleDegrees, axis[0], axis[1], axis[2]);
}
if (node.scale.size() == 3) {
glScaled(node.scale[0], node.scale[1], node.scale[2]);
}
}

18675
json.hpp

File diff suppressed because it is too large Load Diff

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>
@@ -325,15 +322,6 @@ static void DumpStringIntMap(const std::map<std::string, int> &m, int indent) {
}
}
static void DumpExtensions(const tinygltf::ExtensionMap &extension,
const int indent) {
// TODO(syoyo): pritty print Value
for (auto &e : extension) {
std::cout << Indent(indent) << e.first << std::endl;
std::cout << PrintValue("extensions", e.second, indent + 1) << std::endl;
}
}
static void DumpPrimitive(const tinygltf::Primitive &primitive, int indent) {
std::cout << Indent(indent) << "material : " << primitive.material
<< std::endl;
@@ -345,41 +333,50 @@ static void DumpPrimitive(const tinygltf::Primitive &primitive, int indent) {
<< std::endl;
DumpStringIntMap(primitive.attributes, indent + 1);
DumpExtensions(primitive.extensions, indent);
std::cout << Indent(indent) << "extras :" << std::endl
<< PrintValue("extras", primitive.extras, indent + 1) << std::endl;
}
if (!primitive.extensions_json_string.empty()) {
std::cout << Indent(indent + 1) << "extensions(JSON string) = "
<< primitive.extensions_json_string << "\n";
}
if (!primitive.extras_json_string.empty()) {
std::cout << Indent(indent + 1)
<< "extras(JSON string) = " << primitive.extras_json_string
<< "\n";
static void DumpExtensions(const tinygltf::ExtensionMap &extension,
const int indent) {
// TODO(syoyo): pritty print Value
for (auto &e : extension) {
std::cout << Indent(indent) << e.first << std::endl;
std::cout << PrintValue("extensions", e.second, indent + 1) << std::endl;
}
}
static void DumpKHRTextureTransform(const tinygltf::KHRTextureTransform &texform,
const int indent) {
assert(texform.offset.size() == 2);
std::cout << Indent(indent) << "offset : [" << texform.offset[0] << ", " << texform.offset[1] << "]\n";
std::cout << Indent(indent) << "rotation : " << texform.rotation << "\n";
std::cout << Indent(indent) << "scale : [" << texform.scale[0] << ", " << texform.scale[1] << "]\n";
if (texform.texCoord >= 0) {
std::cout << Indent(indent) << "texCoord : TEXCOORD_" << texform.texCoord << "\n";
}
std::cout << Indent(indent) << "extensions(items=" << texform.extensions.size() << ")"
<< std::endl;
DumpExtensions(texform.extensions, indent + 1);
std::cout << PrintValue("extras", texform.extras, indent + 1) << "\n";
}
static void DumpTextureInfo(const tinygltf::TextureInfo &texinfo,
const int indent) {
std::cout << Indent(indent) << "index : " << texinfo.index << "\n";
std::cout << Indent(indent) << "texCoord : TEXCOORD_" << texinfo.texCoord
<< "\n";
if (texinfo.KHR_textureTransform.enabled) {
std::cout << Indent(indent) << "KHR_texture_transform\n";
DumpKHRTextureTransform(texinfo.KHR_textureTransform, indent + 1);
}
std::cout << Indent(indent) << "extensions(items=" << texinfo.extensions.size() << ")"
<< std::endl;
DumpExtensions(texinfo.extensions, indent + 1);
std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
if (!texinfo.extensions_json_string.empty()) {
std::cout << Indent(indent)
<< "extensions(JSON string) = " << texinfo.extensions_json_string
<< "\n";
}
if (!texinfo.extras_json_string.empty()) {
std::cout << Indent(indent)
<< "extras(JSON string) = " << texinfo.extras_json_string << "\n";
}
}
static void DumpNormalTextureInfo(const tinygltf::NormalTextureInfo &texinfo,
@@ -388,6 +385,14 @@ static void DumpNormalTextureInfo(const tinygltf::NormalTextureInfo &texinfo,
std::cout << Indent(indent) << "texCoord : TEXCOORD_" << texinfo.texCoord
<< "\n";
std::cout << Indent(indent) << "scale : " << texinfo.scale << "\n";
if (texinfo.KHR_textureTransform.enabled) {
std::cout << Indent(indent) << "KHR_texture_transform\n";
DumpKHRTextureTransform(texinfo.KHR_textureTransform, indent + 1);
}
std::cout << Indent(indent) << "extensions(items=" << texinfo.extensions.size() << ")"
<< std::endl;
DumpExtensions(texinfo.extensions, indent + 1);
std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
}
@@ -398,6 +403,9 @@ static void DumpOcclusionTextureInfo(
std::cout << Indent(indent) << "texCoord : TEXCOORD_" << texinfo.texCoord
<< "\n";
std::cout << Indent(indent) << "strength : " << texinfo.strength << "\n";
std::cout << Indent(indent) << "extensions(items=" << texinfo.extensions.size() << ")"
<< std::endl;
DumpExtensions(texinfo.extensions, indent + 1);
std::cout << PrintValue("extras", texinfo.extras, indent + 1) << "\n";
}
@@ -417,10 +425,35 @@ static void DumpPbrMetallicRoughness(const tinygltf::PbrMetallicRoughness &pbr,
std::cout << Indent(indent) << "metallicRoughnessTexture :\n";
DumpTextureInfo(pbr.metallicRoughnessTexture, indent + 1);
std::cout << Indent(indent) << "extensions(items=" << pbr.extensions.size() << ")"
<< std::endl;
DumpExtensions(pbr.extensions, indent + 1);
std::cout << PrintValue("extras", pbr.extras, indent + 1) << "\n";
}
static void DumpKHRPbrMetallicRoughness(const tinygltf::KHRPbrSpecularGlossiness &pbr,
const int indent) {
std::cout << Indent(indent)
<< "diffuseFactor : " << PrintFloatArray(pbr.diffuseFactor)
<< "\n";
std::cout << Indent(indent) << "diffuseTexture :\n";
DumpTextureInfo(pbr.diffuseTexture, indent + 1);
std::cout << Indent(indent) << "specularFactor : " << PrintFloatArray(pbr.specularFactor) << "\n";
std::cout << Indent(indent) << "glossinessFactor : " << pbr.glossinessFactor
<< "\n";
std::cout << Indent(indent) << "specularGlossinessTexture :\n";
DumpTextureInfo(pbr.specularGlossinessTexture, indent + 1);
std::cout << Indent(indent) << "extensions(items=" << pbr.extensions.size() << ")"
<< std::endl;
DumpExtensions(pbr.extensions, indent + 1);
std::cout << PrintValue("extras", pbr.extras, indent + 1) << "\n";
}
static void Dump(const tinygltf::Model &model) {
std::cout << "=== Dump glTF ===" << std::endl;
std::cout << "asset.copyright : " << model.asset.copyright
@@ -441,6 +474,9 @@ static void Dump(const tinygltf::Model &model) {
for (size_t i = 0; i < model.scenes.size(); i++) {
std::cout << Indent(1) << "scene[" << i
<< "] name : " << model.scenes[i].name << std::endl;
std::cout << Indent(1) << "extensions(items=" << model.scenes[i].extensions.size() << ")"
<< std::endl;
DumpExtensions(model.scenes[i].extensions, 1);
}
}
@@ -527,7 +563,7 @@ static void Dump(const tinygltf::Model &model) {
<< std::endl;
std::cout << Indent(1) << "channels : [ " << std::endl;
for (size_t j = 0; j < animation.channels.size(); j++) {
for (size_t j = 0; i < animation.channels.size(); i++) {
std::cout << Indent(2)
<< "sampler : " << animation.channels[j].sampler
<< std::endl;
@@ -573,21 +609,6 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(2)
<< "target : " << PrintTarget(bufferView.target)
<< std::endl;
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(bufferView.extensions, 1);
std::cout << PrintValue("extras", bufferView.extras, 2) << std::endl;
if (!bufferView.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< bufferView.extensions_json_string << "\n";
}
if (!bufferView.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << bufferView.extras_json_string
<< "\n";
}
}
}
@@ -598,21 +619,6 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(1) << "name : " << buffer.name << std::endl;
std::cout << Indent(2) << "byteLength : " << buffer.data.size()
<< std::endl;
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(buffer.extensions, 1);
std::cout << PrintValue("extras", buffer.extras, 2) << std::endl;
if (!buffer.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< buffer.extensions_json_string << "\n";
}
if (!buffer.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << buffer.extras_json_string
<< "\n";
}
}
}
@@ -646,6 +652,15 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(1) << "emissiveTexture :\n";
DumpTextureInfo(material.emissiveTexture, 2);
if (material.KHR_materials_unlit) {
std::cout << Indent(1) << "KHR_materials_unlit : true\n";
}
if (material.KHR_pbrSpecularGlossiness.enabled) {
std::cout << Indent(1) << "KHR_materials_pbrSpecularGlossinesst\n";
DumpKHRPbrMetallicRoughness(material.KHR_pbrSpecularGlossiness, 2);
}
std::cout << Indent(1) << "---- legacy material parameter ----\n";
std::cout << Indent(1) << "values(items=" << material.values.size() << ")"
<< std::endl;
@@ -657,19 +672,10 @@ static void Dump(const tinygltf::Model &model) {
}
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(material.extensions, 1);
std::cout << Indent(1) << "extensions(items=" << material.extensions.size() << ")"
<< std::endl;
DumpExtensions(material.extensions, 2);
std::cout << PrintValue("extras", material.extras, 2) << std::endl;
if (!material.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< material.extensions_json_string << "\n";
}
if (!material.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << material.extras_json_string
<< "\n";
}
}
}
@@ -692,19 +698,10 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(2) << "width : " << image.width << std::endl;
std::cout << Indent(2) << "height : " << image.height << std::endl;
std::cout << Indent(2) << "component : " << image.component << std::endl;
std::cout << Indent(1) << "extensions(items=" << image.extensions.size() << ")"
<< std::endl;
DumpExtensions(image.extensions, 1);
std::cout << PrintValue("extras", image.extras, 2) << std::endl;
if (!image.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< image.extensions_json_string << "\n";
}
if (!image.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << image.extras_json_string
<< "\n";
}
}
}
@@ -716,19 +713,11 @@ static void Dump(const tinygltf::Model &model) {
<< std::endl;
std::cout << Indent(1) << "source : " << texture.source
<< std::endl;
std::cout << Indent(1) << "extensions(items=" << texture.extensions.size() << ")"
<< std::endl;
DumpExtensions(texture.extensions, 1);
std::cout << PrintValue("extras", texture.extras, 2) << std::endl;
if (!texture.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< texture.extensions_json_string << "\n";
}
if (!texture.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << texture.extras_json_string
<< "\n";
}
std::cout << "\n";
}
}
@@ -750,20 +739,6 @@ static void Dump(const tinygltf::Model &model) {
std::cout << Indent(2)
<< "wrapT : " << PrintWrapMode(sampler.wrapT)
<< std::endl;
DumpExtensions(sampler.extensions, 1);
std::cout << PrintValue("extras", sampler.extras, 2) << std::endl;
if (!sampler.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< sampler.extensions_json_string << "\n";
}
if (!sampler.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << sampler.extras_json_string
<< "\n";
}
}
}
@@ -796,54 +771,6 @@ static void Dump(const tinygltf::Model &model) {
<< "znear : " << camera.orthographic.znear
<< std::endl;
}
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(camera.extensions, 1);
std::cout << PrintValue("extras", camera.extras, 2) << std::endl;
if (!camera.extensions_json_string.empty()) {
std::cout << Indent(2) << "extensions(JSON string) = "
<< camera.extensions_json_string << "\n";
}
if (!camera.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << camera.extras_json_string
<< "\n";
}
}
}
{
std::cout << "skins(items=" << model.skins.size() << ")" << std::endl;
for (size_t i = 0; i < model.skins.size(); i++) {
const tinygltf::Skin &skin = model.skins[i];
std::cout << Indent(1) << "name : " << skin.name << std::endl;
std::cout << Indent(2)
<< "inverseBindMatrices : " << skin.inverseBindMatrices
<< std::endl;
std::cout << Indent(2) << "skeleton : " << skin.skeleton
<< std::endl;
std::cout << Indent(2)
<< "joints : " << PrintIntArray(skin.joints)
<< std::endl;
std::cout << Indent(1) << "-------------------------------------\n";
DumpExtensions(skin.extensions, 1);
std::cout << PrintValue("extras", skin.extras, 2) << std::endl;
if (!skin.extensions_json_string.empty()) {
std::cout << Indent(2)
<< "extensions(JSON string) = " << skin.extensions_json_string
<< "\n";
}
if (!skin.extras_json_string.empty()) {
std::cout << Indent(2)
<< "extras(JSON string) = " << skin.extras_json_string
<< "\n";
}
}
}
@@ -855,218 +782,12 @@ 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");
exit(1);
}
// Store original JSON string for `extras` and `extensions`
bool store_original_json_for_extras_and_extensions = false;
if (argc > 2) {
store_original_json_for_extras_and_extensions = true;
}
tinygltf::Model model;
tinygltf::TinyGLTF gltf_ctx;
std::string err;
@@ -1074,9 +795,6 @@ int main(int argc, char **argv) {
std::string input_filename(argv[1]);
std::string ext = GetFilePathExtension(input_filename);
gltf_ctx.SetStoreOriginalJSONForExtrasAndExtensions(
store_original_json_for_extras_and_extensions);
bool ret = false;
if (ext.compare("glb") == 0) {
std::cout << "Reading binary glTF" << std::endl;
@@ -1103,20 +821,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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 B

View File

@@ -1,171 +0,0 @@
{
"asset": {
"version": "2.0"
},
"scenes": [
{
"nodes": [
0
]
}
],
"scene": 0,
"nodes": [
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 2,
"POSITION": 1,
"TEXCOORD_0": 3
},
"indices": 0,
"mode": 4,
"material": 0
}
]
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0
},
"baseColorFactor": [
1,
1,
1,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"emissiveFactor": [
0,
0,
0
],
"alphaMode": "OPAQUE"
}
],
"textures": [
{
"source": 0,
"sampler": 0
}
],
"samplers": [
{
"wrapS": 33071,
"wrapT": 33071
}
],
"images": [
{
"uri": " 2x2 image has multiple spaces.png"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5121,
"count": 36,
"normalized": false,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
1,
1,
1
],
"min": [
-1,
-1,
-1
],
"type": "VEC3"
},
{
"bufferView": 3,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
1,
1
],
"min": [
0,
0
],
"type": "VEC2"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 36
},
{
"buffer": 0,
"byteOffset": 36,
"byteLength": 288
},
{
"buffer": 0,
"byteOffset": 324,
"byteLength": 288
},
{
"buffer": 0,
"byteOffset": 612,
"byteLength": 192
}
],
"buffers": [
{
"byteLength": 804,
"uri": "CubeImageUriSpaces.bin"
}
]
}

View File

@@ -1,171 +0,0 @@
{
"asset": {
"version": "2.0"
},
"scenes": [
{
"nodes": [
0
]
}
],
"scene": 0,
"nodes": [
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 2,
"POSITION": 1,
"TEXCOORD_0": 3
},
"indices": 0,
"mode": 4,
"material": 0
}
]
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0
},
"baseColorFactor": [
1,
1,
1,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"emissiveFactor": [
0,
0,
0
],
"alphaMode": "OPAQUE"
}
],
"textures": [
{
"source": 0,
"sampler": 0
}
],
"samplers": [
{
"wrapS": 33071,
"wrapT": 33071
}
],
"images": [
{
"uri": "2x2 image has spaces.png"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5121,
"count": 36,
"normalized": false,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
1,
1,
1
],
"min": [
-1,
-1,
-1
],
"type": "VEC3"
},
{
"bufferView": 3,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"normalized": false,
"max": [
1,
1
],
"min": [
0,
0
],
"type": "VEC2"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 36
},
{
"buffer": 0,
"byteOffset": 36,
"byteLength": 288
},
{
"buffer": 0,
"byteOffset": 324,
"byteLength": 288
},
{
"buffer": 0,
"byteOffset": 612,
"byteLength": 192
}
],
"buffers": [
{
"byteLength": 804,
"uri": "CubeImageUriSpaces.bin"
}
]
}

View File

@@ -1,376 +0,0 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 8,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
},
{
"bufferView": 1,
"componentType": 5125,
"count": 36,
"type": "SCALAR"
}
],
"asset": {
"copyright": "NVIDIA Corporation",
"generator": "Iray glTF plugin",
"version": "2.0"
},
"bufferViews": [
{
"buffer": 0,
"byteLength": 96,
"byteStride": 12
},
{
"buffer": 0,
"byteLength": 144,
"byteOffset": 96
}
],
"buffers": [
{
"byteLength": 240,
"uri": "issue-261.bin"
}
],
"cameras": [
{
"extensions": {
"NV_Iray": {
"mip_burn_highlights": 0.699999988079071,
"mip_burn_highlights_per_component": false,
"mip_camera_shutter": 4.0,
"mip_cm2_factor": 1.0,
"mip_crush_blacks": 0.5,
"mip_f_number": 1.0,
"mip_film_iso": 100.0,
"mip_gamma": 2.200000047683716,
"mip_saturation": 1.0,
"mip_vignetting": 0.00019999999494757503,
"mip_whitepoint": [
1.0,
1.0,
1.0,
1.0
],
"tm_enable_tonemapper": true,
"tm_tonemapper": "mia_exposure_photographic"
}
},
"extras": {
"resolution": [
640,
480
]
},
"name": "default",
"perspective": {
"aspectRatio": 1.3333333730697632,
"yfov": 0.9272952079772949,
"zfar": 1000.0,
"znear": 0.1
},
"type": "perspective"
}
],
"extensions": {
"KHR_lights_punctual": {
"lights": [
{
"color": [
1.0,
1.0,
1.0
],
"intensity": 1000.0,
"name": "light",
"type": "point"
}
]
},
"NV_MDL": {
"modules": [
"mdl::base",
"mdl::nvidia::core_definitions",
"mdl::state"
],
"shaders": [
{
"definition": "mdl::base::environment_spherical(texture_2d)",
"name": "env_shd"
},
{
"arguments": {
"base_color": [
0.0055217444896698,
0.36859095096588135,
0.0041161770932376385
],
"normal=": 2
},
"definition": "mdl::nvidia::core_definitions::flex_material",
"name": "cube_instance_material"
},
{
"definition": "mdl::state::normal()",
"name": "mdl::state::normal_341"
},
{
"arguments": {
"base_color": [
0.0055217444896698,
0.36859095096588135,
0.0041161770932376385
],
"normal=": 4
},
"definition": "mdl::nvidia::core_definitions::flex_material",
"name": "cube_instance_material"
},
{
"definition": "mdl::state::normal()",
"name": "mdl::state::normal_341"
}
]
}
},
"extensionsUsed": [
"NV_MDL",
"NV_Iray",
"KHR_lights_punctual"
],
"materials": [
{
"doubleSided": true,
"extras": {
"mdl_shader": 1
},
"name": "cube_instance_material",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.0055217444896698,
0.36859095096588135,
0.0041161770932376385,
1.0
]
}
}
],
"meshes": [
{
"name": "cube",
"primitives": [
{
"attributes": {
"POSITION": 0
},
"indices": 1,
"material": 0,
"mode": 4
}
]
}
],
"nodes": [
{
"camera": 0,
"extensions": {
"NV_Iray": {
"iview:fkey": -1,
"iview:fov": 53.130104064941406,
"iview:interest": [
0.1342654824256897,
-0.14356137812137604,
0.037264324724674225
],
"iview:position": [
0.9729073643684387,
1.2592438459396362,
2.4199187755584717
],
"iview:roll": 0.0,
"iview:up": [
0.0,
1.0,
0.0
]
}
},
"matrix": [
0.9432751389105357,
-1.1769874756875739e-16,
-0.3320120665176343,
0.0,
-0.16119596696756341,
0.8742297945345237,
-0.45797175303889276,
0.0,
0.290254840694694,
0.48551237507207207,
0.8246392308792816,
0.0,
0.9729073377709113,
1.2592438262175363,
2.419918748461627,
1.0
],
"name": "CamInst"
},
{
"extensions": {
"NV_Iray": {
"caustic": true,
"caustic_cast": true,
"caustic_recv": true,
"face_back": true,
"face_front": true,
"finalgather": true,
"finalgather_cast": true,
"finalgather_recv": true,
"globillum": true,
"globillum_cast": true,
"globillum_recv": true,
"material=": 3,
"pickable": true,
"reflection_cast": true,
"reflection_recv": true,
"refraction_cast": true,
"refraction_recv": true,
"shadow_cast": true,
"shadow_recv": true,
"transparency_cast": true,
"transparency_recv": true,
"visible": true
}
},
"mesh": 0,
"name": "cube_instance"
},
{
"extensions": {
"KHR_lights_punctual": {
"light": 0
},
"NV_Iray": {
"shadow_cast": true,
"visible": false
}
},
"matrix": [
0.6988062355563571,
-7.76042172309776e-17,
-0.7153110128800992,
-0.0,
-0.4276881690736487,
0.8015668284138362,
-0.41781987700564616,
-0.0,
0.57336957992379,
0.5979051928078428,
0.5601398979107212,
0.0,
2.3640632834071384,
2.465226204472449,
2.309515908392224,
1.0
],
"name": "light_inst"
}
],
"scene": 0,
"scenes": [
{
"extensions": {
"NV_Iray": {
"CP_canny_threshold1": 40,
"CP_canny_threshold2": 120,
"CP_color_quantization": 8,
"IVP_color": [
1.0,
0.0,
0.0,
1.0
],
"TM_drago_bias": 0.8500000238418579,
"TM_drago_gamma2": 2.200000047683716,
"TM_drago_saturation": 1.0,
"TM_durand_contrast": 4.0,
"TM_durand_gamma": 2.200000047683716,
"TM_durand_saturation": 1.0,
"TM_durand_sigma_color": 2.0,
"TM_durand_sigma_space": 2.0,
"TM_linear_gamma": 2.200000047683716,
"TM_reinhard_color_adapt": 0.8999999761581421,
"TM_reinhard_gamma": 1.0,
"TM_reinhard_intensity": 0.0,
"TM_reinhard_light_adapt": 1.0,
"TM_reye_Ywhite": 1000000.0,
"TM_reye_bsize": 2,
"TM_reye_bthres": 3.0,
"TM_reye_gamma": 2.200000047683716,
"TM_reye_key": 0.5,
"TM_reye_saturation": 1.0,
"TM_reye_whitebalance": [
1.0,
0.9965101480484009,
0.9805564880371094,
0.0
],
"environment_dome_depth": 200.0,
"environment_dome_height": 200.0,
"environment_dome_mode": "infinite",
"environment_dome_position": [
0.0,
0.0,
0.0
],
"environment_dome_radius": 100.0,
"environment_dome_rotation_angle": 0.0,
"environment_dome_width": 200.0,
"environment_function=": 0,
"environment_function_intensity": 1.9900000095367432,
"iray_instancing": "off",
"iview::inline_color": [
1.0,
0.0,
0.0,
1.0
],
"iview::inline_width": 1.0,
"iview::magnifier_size": 300,
"iview::offset": 10.0,
"iview::outline_color": [
0.0,
0.0,
0.0,
1.0
],
"iview::outline_width": 2.0,
"iview::overview": true,
"iview::zoom_factor": 1.0,
"samples": 4.0,
"shadow_cast": true,
"shadow_recv": true
}
},
"nodes": [
0,
1,
2
]
}
]
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,168 +1,66 @@
#!/usr/bin/env python
# Assume python 2.6 or 2.7
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,13 +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
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

View File

@@ -1,57 +0,0 @@
# Fuzzing test
Do fuzzing test for TinyGLTF API.
## Supported API
* [x] LoadASCIIFromString
* [ ] LoadBinaryFromMemory
### Custom JSON backend (`tinygltf_json.h`)
* [x] LoadASCIIFromString
* [x] LoadBinaryFromMemory
## Requirements
* meson
* clang with fuzzer support(`-fsanitize=fuzzer`. at least clang 8.0 should work)
## Setup
### Ubuntu 18.04
```
$ sudo apt install clang++-8
$ sudo apt install libfuzzer-8-dev
```
Optionally, if you didn't set `update-alternatives` you can set `clang++` to point to `clang++8`
```
$ sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 10
$ sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 10
```
## How to compile
```
$ CXX=clang++ CC=clang meson build
$ 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,33 +0,0 @@
#include <cstdint>
#include <cstring>
#include <memory>
#include <vector>
#include <iostream>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define TINYGLTF_IMPLEMENTATION
#include "tiny_gltf.h"
static void parse_intCoding4(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, size, /* base_dir */"" );
(void)ret;
}
extern "C"
int LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size)
{
parse_intCoding4(data, size);
return 0;
}

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

@@ -1,15 +0,0 @@
project('fuzz_tinygltf', 'cpp', default_options : ['cpp_std=c++11'])
incdirs = include_directories('../../')
executable('fuzz_gltf',
'fuzz_gltf.cc',
include_directories : incdirs,
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' )

View File

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

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,772 +0,0 @@
#include "tiny_gltf_v3.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ===== Digest helpers (used to compare v1 vs v3 parses) ===================== */
static uint64_t fnv64(const uint8_t *data, uint64_t n) {
uint64_t h = 0xcbf29ce484222325ULL;
uint64_t i;
for (i = 0; i < n; ++i) { h ^= data[i]; h *= 0x100000001b3ULL; }
return h;
}
static void d_str(const tg3_str *s) {
uint32_t i;
putchar('"');
if (s && s->data) {
for (i = 0; i < s->len; ++i) {
unsigned char c = (unsigned char)s->data[i];
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, uint32_t n) {
uint32_t i;
putchar('[');
for (i = 0; i < n; ++i) { if (i) putchar(','); d_dbl(v[i]); }
putchar(']');
}
static int cmp_str_int_pair(const void *a, const void *b) {
const tg3_str_int_pair *pa = (const tg3_str_int_pair *)a;
const tg3_str_int_pair *pb = (const tg3_str_int_pair *)b;
uint32_t la = pa->key.len, lb = pb->key.len;
uint32_t m = la < lb ? la : lb;
int r = memcmp(pa->key.data, pb->key.data, m);
if (r) return r;
return (la < lb) ? -1 : (la > lb ? 1 : 0);
}
static void d_attrs(const tg3_str_int_pair *attrs, uint32_t n) {
tg3_str_int_pair *sorted;
uint32_t i;
if (n == 0) { fputs("[]", stdout); return; }
sorted = (tg3_str_int_pair *)malloc(n * sizeof(*sorted));
memcpy(sorted, attrs, n * sizeof(*sorted));
qsort(sorted, n, sizeof(*sorted), cmp_str_int_pair);
putchar('[');
for (i = 0; i < n; ++i) {
if (i) putchar(',');
printf("%.*s:%d", (int)sorted[i].key.len, sorted[i].key.data, sorted[i].value);
}
putchar(']');
free(sorted);
}
static void print_digest(const tg3_model *m) {
uint32_t i, j;
printf("DIGEST_BEGIN\n");
printf("asset version=");
d_str(&m->asset.version);
printf(" generator=");
d_str(&m->asset.generator);
printf("\n");
for (i = 0; i < m->buffers_count; ++i) {
const tg3_buffer *b = &m->buffers[i];
uint64_t h = b->data.data ? fnv64(b->data.data, b->data.count) : 0;
printf("buffer %u byte_length=%llu fnv64=0x%016llx\n",
i, (unsigned long long)b->data.count, (unsigned long long)h);
}
for (i = 0; i < m->buffer_views_count; ++i) {
const tg3_buffer_view *bv = &m->buffer_views[i];
printf("buffer_view %u buffer=%d byte_offset=%llu byte_length=%llu byte_stride=%u\n",
i, bv->buffer, (unsigned long long)bv->byte_offset,
(unsigned long long)bv->byte_length, bv->byte_stride);
}
for (i = 0; i < m->accessors_count; ++i) {
const tg3_accessor *a = &m->accessors[i];
printf("accessor %u buffer_view=%d byte_offset=%llu component_type=%d count=%llu type=%d normalized=%d min=",
i, a->buffer_view, (unsigned long long)a->byte_offset, a->component_type,
(unsigned long long)a->count, a->type, a->normalized);
d_dbl_arr(a->min_values, a->min_values_count);
printf(" max=");
d_dbl_arr(a->max_values, a->max_values_count);
printf(" sparse=%d\n", a->sparse.is_sparse);
}
for (i = 0; i < m->meshes_count; ++i) {
const tg3_mesh *me = &m->meshes[i];
printf("mesh %u primitives_count=%u weights_count=%u\n",
i, me->primitives_count, me->weights_count);
for (j = 0; j < me->primitives_count; ++j) {
const tg3_primitive *p = &me->primitives[j];
printf("prim %u %u indices=%d material=%d mode=%d attrs=", i, j,
p->indices, p->material, p->mode);
d_attrs(p->attributes, p->attributes_count);
printf(" targets_count=%u\n", p->targets_count);
}
}
for (i = 0; i < m->nodes_count; ++i) {
const tg3_node *n = &m->nodes[i];
printf("node %u mesh=%d skin=%d camera=%d light=%d children_count=%u has_matrix=%d t=",
i, n->mesh, n->skin, n->camera, n->light, n->children_count, n->has_matrix);
d_dbl_arr(n->translation, 3);
printf(" r=");
d_dbl_arr(n->rotation, 4);
printf(" s=");
d_dbl_arr(n->scale, 3);
printf(" matrix=");
d_dbl_arr(n->matrix, 16);
printf(" weights_count=%u\n", n->weights_count);
}
for (i = 0; i < m->materials_count; ++i) {
const tg3_material *mat = &m->materials[i];
printf("material %u alpha_mode=", i);
d_str(&mat->alpha_mode);
printf(" alpha_cutoff=");
d_dbl(mat->alpha_cutoff);
printf(" double_sided=%d emissive=", mat->double_sided);
d_dbl_arr(mat->emissive_factor, 3);
printf(" base_color_factor=");
d_dbl_arr(mat->pbr_metallic_roughness.base_color_factor, 4);
printf(" metallic=");
d_dbl(mat->pbr_metallic_roughness.metallic_factor);
printf(" roughness=");
d_dbl(mat->pbr_metallic_roughness.roughness_factor);
printf(" base_color_tex=%d normal_tex=%d occlusion_tex=%d emissive_tex=%d\n",
mat->pbr_metallic_roughness.base_color_texture.index,
mat->normal_texture.index,
mat->occlusion_texture.index,
mat->emissive_texture.index);
}
for (i = 0; i < m->textures_count; ++i) {
const tg3_texture *t = &m->textures[i];
printf("texture %u source=%d sampler=%d\n", i, t->source, t->sampler);
}
for (i = 0; i < m->samplers_count; ++i) {
const tg3_sampler *s = &m->samplers[i];
printf("sampler %u min_filter=%d mag_filter=%d wrap_s=%d wrap_t=%d\n",
i, s->min_filter, s->mag_filter, s->wrap_s, s->wrap_t);
}
for (i = 0; i < m->images_count; ++i) {
const tg3_image *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 %u buffer_view=%d\n", i, im->buffer_view);
}
for (i = 0; i < m->skins_count; ++i) {
const tg3_skin *s = &m->skins[i];
printf("skin %u inverse_bind_matrices=%d skeleton=%d joints_count=%u\n",
i, s->inverse_bind_matrices, s->skeleton, s->joints_count);
}
for (i = 0; i < m->animations_count; ++i) {
const tg3_animation *a = &m->animations[i];
printf("animation %u channels_count=%u samplers_count=%u\n",
i, a->channels_count, a->samplers_count);
for (j = 0; j < a->channels_count; ++j) {
const tg3_animation_channel *c = &a->channels[j];
printf("chan %u %u sampler=%d target_node=%d target_path=", i, j,
c->sampler, c->target.node);
d_str(&c->target.path);
printf("\n");
}
for (j = 0; j < a->samplers_count; ++j) {
const tg3_animation_sampler *as = &a->samplers[j];
printf("samp %u %u input=%d output=%d interpolation=", i, j,
as->input, as->output);
d_str(&as->interpolation);
printf("\n");
}
}
for (i = 0; i < m->cameras_count; ++i) {
const tg3_camera *c = &m->cameras[i];
int is_persp = (c->type.len == 11 && memcmp(c->type.data, "perspective", 11) == 0);
printf("camera %u 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.aspect_ratio);
} 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 (i = 0; i < m->scenes_count; ++i) {
const tg3_scene *s = &m->scenes[i];
printf("scene %u nodes_count=%u\n", i, s->nodes_count);
}
printf("DIGEST_END\n");
}
/* ============================================================================ */
static int mem_contains(const uint8_t *data, uint64_t size, const char *needle) {
size_t needle_len = strlen(needle);
uint64_t i;
if (needle_len == 0 || size < (uint64_t)needle_len) {
return 0;
}
for (i = 0; i + (uint64_t)needle_len <= size; ++i) {
if (memcmp(data + i, needle, needle_len) == 0) {
return 1;
}
}
return 0;
}
static int check_minimal_parse(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"scene\":0,"
"\"scenes\":[{\"nodes\":[0]}],"
"\"nodes\":[{\"name\":\"root\"}]}";
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) {
fprintf(stderr, "tg3_parse_auto failed: %d\n", (int)err);
tg3_error_stack_free(&errors);
return 0;
}
if (model.default_scene != 0 || model.scenes_count != 1 || model.nodes_count != 1) {
fprintf(stderr, "unexpected parsed model shape\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (!model.nodes || !model.nodes[0].name.data ||
!tg3_str_equals_cstr(model.nodes[0].name, "root")) {
fprintf(stderr, "node name mismatch\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_minimal_write_roundtrip(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"scene\":0,"
"\"scenes\":[{\"nodes\":[0]}],"
"\"nodes\":[{\"name\":\"root\"}]}";
tg3_model model;
tg3_model roundtrip;
tg3_error_stack errors;
tg3_parse_options parse_opts;
tg3_write_options write_opts;
tg3_error_code err;
uint8_t *out = NULL;
uint64_t out_size = 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&parse_opts);
tg3_write_options_init(&write_opts);
err = tg3_parse_auto(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0,
&parse_opts);
if (err != TG3_OK) {
fprintf(stderr, "initial parse failed: %d\n", (int)err);
tg3_error_stack_free(&errors);
return 0;
}
err = tg3_write_to_memory(&model, &errors, &out, &out_size, &write_opts);
if (err != TG3_OK || !out || out_size == 0) {
fprintf(stderr, "tg3_write_to_memory failed: %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (!mem_contains(out, out_size, "\"asset\"") ||
!mem_contains(out, out_size, "\"root\"")) {
fprintf(stderr, "serialized JSON missing expected fields\n");
tg3_write_free(out, &write_opts);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
err = tg3_parse_auto(&roundtrip, &errors, out, out_size, "", 0, &parse_opts);
tg3_write_free(out, &write_opts);
if (err != TG3_OK) {
fprintf(stderr, "roundtrip parse failed: %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (roundtrip.default_scene != 0 || roundtrip.nodes_count != 1 ||
!roundtrip.nodes || !roundtrip.nodes[0].name.data ||
!tg3_str_equals_cstr(roundtrip.nodes[0].name, "root")) {
fprintf(stderr, "roundtrip model mismatch\n");
tg3_model_free(&roundtrip);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&roundtrip);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_parse_file_failure_initializes_model(void) {
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
memset(&model, 0xA5, sizeof(model));
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse_file(&model, &errors,
"tg3-tester-nonexistent-path.gltf", 32, &opts);
if (err != TG3_ERR_FS_NOT_AVAILABLE && err != TG3_ERR_FILE_NOT_FOUND) {
fprintf(stderr, "tg3_parse_file unexpected error: %d\n", (int)err);
tg3_error_stack_free(&errors);
return 0;
}
if (model.default_scene != -1) {
fprintf(stderr, "tg3_parse_file did not initialize model on failure\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_non_object_root_rejected(void) {
static const uint8_t json[] = "\"not an object\"";
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(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_JSON_PARSE) {
fprintf(stderr, "non-object root returned unexpected error: %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (model.default_scene != -1) {
fprintf(stderr, "non-object root left model in unexpected state\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_huge_integer_field_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},\"scene\":6.66667e+70}";
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(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_JSON_PARSE) {
fprintf(stderr, "huge integer-like field returned unexpected error: %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
/* ===== Security regression tests ============================================ */
/* fs read_file callback that records calls into *(int *)user_data and never
* succeeds — used to verify path-traversal URIs never reach the filesystem. */
static int32_t recording_read_file(uint8_t **out_data, uint64_t *out_size,
const char *path, uint32_t path_len,
void *user_data) {
(void)out_data; (void)out_size; (void)path; (void)path_len;
if (user_data) *(int *)user_data += 1;
return 0;
}
static int check_path_traversal_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"uri\":\"../../etc/passwd\",\"byteLength\":4}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
int fs_calls = 0;
uint32_t i;
int saw = 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.fs.read_file = recording_read_file;
opts.fs.user_data = &fs_calls;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1),
"/some/base", 10, &opts);
if (err == TG3_OK) {
fprintf(stderr, "path traversal NOT rejected\n");
goto fail;
}
if (fs_calls != 0) {
fprintf(stderr, "fs.read_file called %d times for traversal URI\n", fs_calls);
goto fail;
}
for (i = 0; i < errors.count; ++i) {
if (errors.entries[i].code == TG3_ERR_INVALID_VALUE) saw = 1;
}
if (!saw) {
fprintf(stderr, "expected TG3_ERR_INVALID_VALUE on traversal\n");
goto fail;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
fail:
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
static int check_absolute_uri_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"uri\":\"/etc/passwd\",\"byteLength\":4}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
int fs_calls = 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
opts.fs.read_file = recording_read_file;
opts.fs.user_data = &fs_calls;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1),
"/base", 5, &opts);
if (err == TG3_OK || fs_calls != 0) {
fprintf(stderr, "absolute URI not rejected (rc=%d, fs_calls=%d)\n",
(int)err, fs_calls);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_negative_byte_stride_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4,\"byteStride\":-1}]}";
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(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err == TG3_OK) {
fprintf(stderr, "negative byteStride NOT rejected\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_oob_index_rejected(void) {
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4}],"
"\"accessors\":[{\"bufferView\":1000000,\"componentType\":5121,"
"\"count\":1,\"type\":\"SCALAR\"}]}";
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(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_INVALID_INDEX) {
fprintf(stderr, "OOB index expected TG3_ERR_INVALID_INDEX, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_oob_index_opt_in(void) {
/* When validate_indices=0, parse should accept the same out-of-range index. */
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4}],"
"\"accessors\":[{\"bufferView\":1000000,\"componentType\":5121,"
"\"count\":1,\"type\":\"SCALAR\"}]}";
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);
opts.validate_indices = 0;
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_OK) {
fprintf(stderr, "validate_indices=0 should accept OOB index, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
if (model.accessors_count != 1 || model.accessors[0].buffer_view != 1000000) {
fprintf(stderr, "OOB index not preserved when validation off\n");
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_extension_index_oob_rejected(void) {
/* MSFT_lod and KHR_audio index fields must be validated when
* validate_indices=1, otherwise downstream consumers can read OOB. */
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"nodes\":[{\"extensions\":{\"MSFT_lod\":{\"ids\":[99999]}}}]}";
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(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err != TG3_ERR_INVALID_INDEX) {
fprintf(stderr, "MSFT_lod OOB index expected TG3_ERR_INVALID_INDEX, got %d\n", (int)err);
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
}
static int check_error_messages_survive_parse_failure(void) {
/* Regression: parse failure must not invalidate arena-allocated error
* message strings on the user's tg3_error_stack before model_free. */
static const uint8_t json[] =
"{\"asset\":{\"version\":\"2.0\"},"
"\"buffers\":[{\"byteLength\":4}],"
"\"bufferViews\":[{\"buffer\":0,\"byteLength\":4,\"byteStride\":-1}]}";
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
uint32_t i;
int seen_stride_msg = 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse(&model, &errors, json, (uint64_t)(sizeof(json) - 1), "", 0, &opts);
if (err == TG3_OK) goto fail;
for (i = 0; i < errors.count; ++i) {
const char *m = errors.entries[i].message;
if (m && strstr(m, "byteStride")) seen_stride_msg = 1;
}
if (!seen_stride_msg) {
fprintf(stderr, "byteStride error message missing or unreadable after parse\n");
goto fail;
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 1;
fail:
tg3_model_free(&model);
tg3_error_stack_free(&errors);
return 0;
}
static int parse_file_arg(const char *path) {
FILE *fp = fopen(path, "rb");
uint8_t *buf;
long sz;
size_t got;
tg3_model model;
tg3_error_stack errors;
tg3_parse_options opts;
tg3_error_code err;
int ok;
const char *slash;
size_t base_len;
if (!fp) {
fprintf(stderr, "open failed: %s\n", path);
return 0;
}
if (fseek(fp, 0, SEEK_END) != 0 || (sz = ftell(fp)) < 0 ||
fseek(fp, 0, SEEK_SET) != 0) {
fprintf(stderr, "seek failed: %s\n", path);
fclose(fp);
return 0;
}
buf = (uint8_t *)malloc((size_t)sz);
if (!buf) {
fprintf(stderr, "alloc failed: %s\n", path);
fclose(fp);
return 0;
}
got = fread(buf, 1, (size_t)sz, fp);
fclose(fp);
if (got != (size_t)sz) {
fprintf(stderr, "short read: %s\n", path);
free(buf);
return 0;
}
slash = strrchr(path, '/');
base_len = slash ? (size_t)(slash - path) : 0;
tg3_error_stack_init(&errors);
tg3_parse_options_init(&opts);
err = tg3_parse_auto(&model, &errors, buf, (uint64_t)sz,
path, (uint32_t)base_len, &opts);
ok = (err == TG3_OK);
if (!ok) {
uint32_t i;
fprintf(stderr, "parse failed (%d): %s\n", (int)err, path);
for (i = 0; i < errors.count; ++i) {
fprintf(stderr, " [%u] code=%d sev=%d path=%s msg=%s offset=%lld\n",
i, (int)errors.entries[i].code, (int)errors.entries[i].severity,
errors.entries[i].json_path ? errors.entries[i].json_path : "",
errors.entries[i].message ? errors.entries[i].message : "",
(long long)errors.entries[i].byte_offset);
}
} else {
printf("COUNTS"
" accessors=%u animations=%u buffers=%u bufferViews=%u"
" cameras=%u images=%u materials=%u meshes=%u nodes=%u"
" samplers=%u scenes=%u skins=%u textures=%u lights=%u\n",
model.accessors_count, model.animations_count,
model.buffers_count, model.buffer_views_count,
model.cameras_count, model.images_count,
model.materials_count, model.meshes_count,
model.nodes_count, model.samplers_count,
model.scenes_count, model.skins_count,
model.textures_count, model.lights_count);
print_digest(&model);
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);
free(buf);
return ok;
}
int main(int argc, char **argv) {
if (argc > 1) {
int i;
for (i = 1; i < argc; ++i) {
if (!parse_file_arg(argv[i])) return 1;
}
return 0;
}
if (!check_minimal_parse()) {
return 1;
}
if (!check_minimal_write_roundtrip()) {
return 1;
}
if (!check_parse_file_failure_initializes_model()) {
return 1;
}
if (!check_non_object_root_rejected()) {
return 1;
}
if (!check_huge_integer_field_rejected()) {
return 1;
}
if (!check_path_traversal_rejected()) {
return 1;
}
if (!check_absolute_uri_rejected()) {
return 1;
}
if (!check_negative_byte_stride_rejected()) {
return 1;
}
if (!check_oob_index_rejected()) {
return 1;
}
if (!check_oob_index_opt_in()) {
return 1;
}
if (!check_extension_index_oob_rejected()) {
return 1;
}
if (!check_error_messages_survive_parse_failure()) {
return 1;
}
return 0;
}

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,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
}

View File

@@ -1,4 +0,0 @@
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "tiny_gltf.h"

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

View File

@@ -1,11 +0,0 @@
WASI_VERSION=16
WASI_VERSION_FULL=${WASI_VERSION}.0
WASI_SDK_PATH=$(HOME)/local/wasi-sdk-${WASI_VERSION_FULL}
CC=${WASI_SDK_PATH}/bin/clang
CXX=${WASI_SDK_PATH}/bin/clang++
CXXFLAGS=-fno-rtti -fno-exceptions -g -Os
all:
$(CXX) ../loader_example.cc $(CXXFLAGS) -I../

View File

@@ -1,31 +0,0 @@
Experimental WASI/WASM build
## Build
Download wasi-sdk https://github.com/WebAssembly/wasi-sdk
Compile tinygltf without C++ exceptions and threads. See `Makefile` for details
(NOTE: TinyGLTF itself does not use RTTI and threading feature(C++ threads, posix, win32 thread))
## Build examples and Run
Build `loader_example.cc`
```
$ /path/to/wasi-sdk-16.0/bin/clang++ ../loader_example.cc -fno-rtti -fno-exceptions -g -Os -I../ -o loader_example.wasi
```
Tested with wasmtime. https://github.com/bytecodealliance/wasmtime
Set a folder containing .gltf file to `--dir`
```
$ wasmtime --dir=../models loader_example.wasi ../models/Cube/Cube.gltf
```
## Emscripen
T.B.W. ...
EoL.