Add pure-C TinyGLTF v3 runtime

Introduce a C-first TinyGLTF v3 runtime in tiny_gltf_v3.c with a pure-C JSON backend, hook the public header to the new implementation, and add CMake/test coverage for parse and write round-trips.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Syoyo Fujita
2026-05-06 04:28:41 +09:00
parent a18f41142f
commit 85441bbe19
8 changed files with 4151 additions and 18 deletions

View File

@@ -65,6 +65,21 @@ if (TINYGLTF_BUILD_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)
#
@@ -107,7 +122,10 @@ if (TINYGLTF_INSTALL)
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

View File

@@ -21,28 +21,36 @@ v3 is a ground-up rewrite with a C-centric, low-overhead design:
### Quick start (v3)
Copy `tiny_gltf_v3.h` and `tinygltf_json.h` to your project. In **one** `.cpp` file:
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.
```cpp
#define TINYGLTF3_IMPLEMENTATION
#define TINYGLTF3_ENABLE_FS // enable file I/O
#define TINYGLTF3_ENABLE_STB_IMAGE // enable image decoding
```c
#include "tiny_gltf_v3.h"
```
Loading a glTF file:
```c
tg3_load_options_t opts = tg3_load_options_default();
tg3_error_stack_t errors = {0};
tg3_model_t *model = tg3_load_from_file("scene.gltf", &opts, &errors);
if (!model) {
for (int i = 0; i < errors.count; i++)
fprintf(stderr, "[%s] %s\n", tg3_severity_str(errors.items[i].severity),
errors.items[i].message);
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_model_free(&model);
tg3_error_stack_free(&errors);
```
## Status

View File

@@ -28,7 +28,7 @@ all: $(GEN) $(BENCH_V3)
$(GEN): gen_synthetic.cpp
$(CXX) $(CXXFLAGS) -o $@ $<
$(BENCH_V3): bench_v3.cpp ../tiny_gltf_v3.h ../tinygltf_json.h
$(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

140
tests/tester_v3_c.c Normal file
View File

@@ -0,0 +1,140 @@
#include "tiny_gltf_v3.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
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;
}
int main(void) {
if (!check_minimal_parse()) {
return 1;
}
if (!check_minimal_write_roundtrip()) {
return 1;
}
return 0;
}

View File

@@ -26,7 +26,7 @@ MAX_TIME ?= 0
all: $(FUZZER)
$(FUZZER): fuzz_gltf_v3.cc ../../../tiny_gltf_v3.h ../../../tinygltf_json.h
$(FUZZER): fuzz_gltf_v3.cc ../../../tiny_gltf_v3.h ../../../tiny_gltf_v3.c ../../../tinygltf_json_c.h
$(CXX) $(CXXFLAGS) $(SANITIZE) $(INCLUDES) -o $@ $<
run: $(FUZZER) | $(CORPUS) $(ARTIFACTS)

2939
tiny_gltf_v3.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
* tiny_gltf_v3.h - Header-only C glTF 2.0 loader and writer (v3)
* tiny_gltf_v3.h - C-first glTF 2.0 loader and writer API (v3)
*
* The MIT License (MIT)
* Copyright (c) 2026 - Present: Syoyo Fujita
@@ -27,7 +27,7 @@
* Version: v3.0.0-alpha
*
* Ground-up C-centric API rewrite of tinygltf.
* Uses tinygltf_json.h as the sole JSON backend.
* The default runtime implementation lives in tiny_gltf_v3.c.
*
* Key differences from v2:
* - Pure C POD structs (no STL containers in public API)
@@ -46,7 +46,7 @@
* Section 2: Configuration Macros
* ====================================================================== */
/* Build mode: define in ONE C++ translation unit (.cpp) */
/* Legacy single-translation-unit build mode: define in ONE C or C++ file */
/* #define TINYGLTF3_IMPLEMENTATION */
/* Opt-in features (OFF by default) */
@@ -1219,6 +1219,11 @@ ParseGenerator tg3_parse_coro(
* ====================================================================== */
#ifdef TINYGLTF3_IMPLEMENTATION
#define TINYGLTF3_SOURCE_INCLUDED_FROM_HEADER 1
#include "tiny_gltf_v3.c"
#undef TINYGLTF3_SOURCE_INCLUDED_FROM_HEADER
#if 0
#if !defined(__cplusplus)
#error "TINYGLTF3_IMPLEMENTATION requires a C++ translation unit (compile as .cpp)"
@@ -4431,6 +4436,7 @@ TINYGLTF3_API void tg3_writer_destroy(tg3_writer *w) {
delete w;
}
#endif /* legacy header-only v3 implementation */
#endif /* TINYGLTF3_IMPLEMENTATION */
#endif /* TINY_GLTF_V3_H_ */

1022
tinygltf_json_c.h Normal file

File diff suppressed because it is too large Load Diff