Skip to content

Add tg3_validate() glTF 2.0 validation API to tiny_gltf_v3#545

Draft
Copilot wants to merge 3 commits intoreleasefrom
copilot/add-gltf-validation-feature
Draft

Add tg3_validate() glTF 2.0 validation API to tiny_gltf_v3#545
Copilot wants to merge 3 commits intoreleasefrom
copilot/add-gltf-validation-feature

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 25, 2026

tinygltf v3 had error codes and an error stack for validation but no post-parse validation function. This adds tg3_validate() — a standalone validator that checks a parsed tg3_model against the glTF 2.0 spec.

API

/* C */
tg3_error_code tg3_validate(const tg3_model *model, tg3_error_stack *errors);

/* C++ wrapper */
tg3_error_code tinygltf3::validate(const Model &model, ErrorStack &errors);

Typical usage: parse first, then validate independently (or validate after programmatic model construction).

tg3_model model;  tg3_error_stack errors;
tg3_error_stack_init(&errors);
tg3_parse_auto(&model, &errors, data, size, "", 0, &opts);
tg3_error_stack_free(&errors);
tg3_error_stack_init(&errors);

if (tg3_validate(&model, &errors) != TG3_OK) {
    for (uint32_t i = 0; i < tg3_errors_count(&errors); i++) {
        const tg3_error_entry *e = tg3_errors_get(&errors, i);
        fprintf(stderr, "%s: %s\n", e->json_path, e->message);
    }
}
tg3_model_free(&model);
tg3_error_stack_free(&errors);

Validation coverage

  • Assetversion required, warns if not "2.0"
  • Buffer views — buffer index, byteOffset+byteLength within buffer bounds, byteStride range [4,252] and 4-alignment (per spec §3.6.2.4)
  • Accessors — valid componentType/type, count ≥ 1, byte bounds and alignment within buffer view, sparse sub-structure refs
  • Meshes — at least one primitive per mesh, attribute/index/material accessor refs, index accessor must be SCALAR+unsigned, mode in [0,6]
  • Nodes — camera/skin/mesh/light refs, children refs, no self-reference
  • Skins — joints non-empty, all joint indices valid, IBM accessor must be MAT4 FLOAT with count ≥ joints
  • Animations — sampler input/output accessor refs, interpolation values, channel target path one of translation|rotation|scale|weights
  • Textures — sampler and source image indices
  • MaterialsalphaMode enum, all texture info indices
  • Camerastype required, perspective yfov/znear > 0, orthographic xmag/ymag ≠ 0, znear ≥ 0, zfar > znear
  • Scenes — node indices
  • ImagesbufferView index, warns on missing URI/bufferView/data

Error message lifetime fix

tg3_error_stack gains an internal msg_arena_ field. tg3_validate() allocates both message and json_path strings into this arena so they remain valid after the function returns. tg3_error_stack_free() destroys it. No change to existing parser behaviour.

Tests

New tests/v3/tester_v3.cc (22 test cases, 53 assertions) and corresponding tester_v3 CMake target.

Original prompt

Add glTF validation feature. for tinygltf v3 only


💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

Copilot AI changed the title [WIP] Add glTF validation feature for tinygltf v3 Add tg3_validate() glTF 2.0 validation API to tiny_gltf_v3 Mar 25, 2026
Copilot AI requested a review from syoyo March 25, 2026 02:36
@syoyo
Copy link
Copy Markdown
Owner

syoyo commented Apr 5, 2026

@copilot review deeply

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 5, 2026

@copilot review deeply

Deep review found and fixed three bugs (commit 87b0d17):

Bug 1 — first_err not updated on material texture errors
tg3__val_check_tex_index pushed errors directly via tg3__val_push, bypassing the TG3__VERR macro that updates first_err. As a result, out-of-range material texture indices would set errors->has_error but tg3_validate() still returned TG3_OK. Fixed by returning int from the helper and OR-ing all five texture-slot calls to conditionally set first_err = TG3_ERR_INVALID_INDEX.

Bug 2 — False-positive buffer-view bounds errors on unloaded buffers
When an external buffer file is never loaded (e.g., no FS callbacks provided), buf->data.count == 0 and buf->data.data == NULL. The check bv->byte_offset + bv->byte_length > 0 was always true, flagging every buffer view as out-of-range. Added a buf->data.data != NULL guard so the bounds check is only performed when data is actually present.

Bug 3 — Dangling stack pointer on arena OOM in tg3__val_push
When tg3__arena_strdup returned NULL under memory pressure, msg remained pointing to the local msg_buf stack variable and was stored in the error-stack entry — a use-after-free. Fixed by dropping the entry entirely when arena duplication fails (silently, under OOM) rather than storing dangling pointers.

Two regression tests were added covering Bugs 1 and 2. All 24 v3 tests (58 assertions) pass; existing 303/321 tests are unaffected.

Copilot finished work on behalf of syoyo April 5, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants