mirror of
https://github.com/syoyo/tinygltf.git
synced 2026-08-24 20:18:32 +00:00
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:
@@ -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
|
||||
|
||||
34
README.md
34
README.md
@@ -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
|
||||
|
||||
@@ -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
140
tests/tester_v3_c.c
Normal 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;
|
||||
}
|
||||
@@ -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
2939
tiny_gltf_v3.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
1022
tinygltf_json_c.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user