Skip to content

Commit

Permalink
Copy extras data into cgltf_extras::data during parsing
Browse files Browse the repository at this point in the history
This change switches cgltf_extras to embed the JSON data the same way we
handle unprocessed extensions, by copying it to heap. While this results
in a few extras heap allocations, in practice the other data such as
object names et al dominates the allocation traffic. The benefit here is
that it makes using the library simpler - extras can be read from
directly without copying, and this means that the original glTF data can
be discarded which can improve memory consumption.

The downside, of course, is that there's quite a few places where extras
is used. This can be mitigated by jkuhlmann#118 in the future, but for now we
diligently free extras for every struct that has them, something that is
made easier by the fact that most structs that contain extras also
contain extensions.

Note that this change leaves start_offset/end_offset in tact for now,
which makes it easy to upgrade to the new version, but in the future we
can remove these as the internals of cgltf no longer depend on those -
cgltf_write can work with manually assembled trees so for now it handles
both ::data and ::start/end_offset.

To make sure this change works well, I had to deduplicate extra data in
material mappings by only recording the extras token and then
redundantly reparsing extras when filling the mappings - something that
in practice is likely benign as almost no assets that use material
mappins contain extras.

Fixes jkuhlmann#116.
  • Loading branch information
zeux committed Sep 16, 2022
1 parent 40ad011 commit 567663a
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 25 deletions.
98 changes: 83 additions & 15 deletions cgltf.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,9 @@
* `cgltf_accessor_read_index` is similar to its floating-point counterpart, but it returns size_t
* and only works with single-component data types.
*
* `cgltf_result cgltf_copy_extras_json(const cgltf_data*, const cgltf_extras*,
* char* dest, cgltf_size* dest_size)` allows users to retrieve the "extras" data that
* can be attached to many glTF objects (which can be arbitrary JSON data). The
* `cgltf_extras` struct stores the offsets of the start and end of the extras JSON data
* as it appears in the complete glTF JSON data. This function copies the extras data
* into the provided buffer. If `dest` is NULL, the length of the data is written into
* `dest_size`. You can then parse this data using your own JSON parser
* `cgltf_copy_extras_json` allows users to retrieve the "extras" data that can be attached to many
* glTF objects (which can be arbitrary JSON data). This is a legacy function, consider using
* cgltf_extras::data directly instead. You can parse this data using your own JSON parser
* or, if you've included the cgltf implementation using the integrated JSMN JSON parser.
*/
#ifndef CGLTF_H_INCLUDED__
Expand Down Expand Up @@ -256,8 +252,10 @@ typedef enum cgltf_data_free_method {
} cgltf_data_free_method;

typedef struct cgltf_extras {
cgltf_size start_offset;
cgltf_size end_offset;
cgltf_size start_offset; /* this field is deprecated and will be removed in the future; use data instead */
cgltf_size end_offset; /* this field is deprecated and will be removed in the future; use data instead */

char* data;
} cgltf_extras;

typedef struct cgltf_extension {
Expand Down Expand Up @@ -839,6 +837,7 @@ cgltf_size cgltf_num_components(cgltf_type type);

cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_float* out, cgltf_size float_count);

/* this function is deprecated and will be removed in the future; use cgltf_extras::data instead */
cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* extras, char* dest, cgltf_size* dest_size);

#ifdef __cplusplus
Expand Down Expand Up @@ -1746,6 +1745,11 @@ cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras*
return cgltf_result_success;
}

static void cgltf_free_extras(cgltf_data* data, cgltf_extras* extras)
{
data->memory.free_func(data->memory.user_data, extras->data);
}

static void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count)
{
for (cgltf_size i = 0; i < extensions_count; ++i)
Expand All @@ -1759,6 +1763,7 @@ static void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions,
static void cgltf_free_texture_view(cgltf_data* data, cgltf_texture_view* view)
{
cgltf_free_extensions(data, view->extensions, view->extensions_count);
cgltf_free_extras(data, &view->extras);
}

void cgltf_free(cgltf_data* data)
Expand All @@ -1776,6 +1781,7 @@ void cgltf_free(cgltf_data* data)
data->memory.free_func(data->memory.user_data, data->asset.min_version);

cgltf_free_extensions(data, data->asset.extensions, data->asset.extensions_count);
cgltf_free_extras(data, &data->asset.extras);

for (cgltf_size i = 0; i < data->accessors_count; ++i)
{
Expand All @@ -1786,8 +1792,12 @@ void cgltf_free(cgltf_data* data)
cgltf_free_extensions(data, data->accessors[i].sparse.extensions, data->accessors[i].sparse.extensions_count);
cgltf_free_extensions(data, data->accessors[i].sparse.indices_extensions, data->accessors[i].sparse.indices_extensions_count);
cgltf_free_extensions(data, data->accessors[i].sparse.values_extensions, data->accessors[i].sparse.values_extensions_count);
cgltf_free_extras(data, &data->accessors[i].sparse.extras);
cgltf_free_extras(data, &data->accessors[i].sparse.indices_extras);
cgltf_free_extras(data, &data->accessors[i].sparse.values_extras);
}
cgltf_free_extensions(data, data->accessors[i].extensions, data->accessors[i].extensions_count);
cgltf_free_extras(data, &data->accessors[i].extras);
}
data->memory.free_func(data->memory.user_data, data->accessors);

Expand All @@ -1797,6 +1807,7 @@ void cgltf_free(cgltf_data* data)
data->memory.free_func(data->memory.user_data, data->buffer_views[i].data);

cgltf_free_extensions(data, data->buffer_views[i].extensions, data->buffer_views[i].extensions_count);
cgltf_free_extras(data, &data->buffer_views[i].extras);
}
data->memory.free_func(data->memory.user_data, data->buffer_views);

Expand All @@ -1816,8 +1827,8 @@ void cgltf_free(cgltf_data* data)
data->memory.free_func(data->memory.user_data, data->buffers[i].uri);

cgltf_free_extensions(data, data->buffers[i].extensions, data->buffers[i].extensions_count);
cgltf_free_extras(data, &data->buffers[i].extras);
}

data->memory.free_func(data->memory.user_data, data->buffers);

for (cgltf_size i = 0; i < data->meshes_count; ++i)
Expand Down Expand Up @@ -1855,9 +1866,15 @@ void cgltf_free(cgltf_data* data)
data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes);
}

for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k)
{
cgltf_free_extras(data, &data->meshes[i].primitives[j].mappings[k].extras);
}

data->memory.free_func(data->memory.user_data, data->meshes[i].primitives[j].mappings);

cgltf_free_extensions(data, data->meshes[i].primitives[j].extensions, data->meshes[i].primitives[j].extensions_count);
cgltf_free_extras(data, &data->meshes[i].primitives[j].extras);
}

data->memory.free_func(data->memory.user_data, data->meshes[i].primitives);
Expand All @@ -1869,6 +1886,7 @@ void cgltf_free(cgltf_data* data)
}

cgltf_free_extensions(data, data->meshes[i].extensions, data->meshes[i].extensions_count);
cgltf_free_extras(data, &data->meshes[i].extras);

data->memory.free_func(data->memory.user_data, data->meshes[i].target_names);
}
Expand Down Expand Up @@ -1924,6 +1942,7 @@ void cgltf_free(cgltf_data* data)
cgltf_free_texture_view(data, &data->materials[i].emissive_texture);

cgltf_free_extensions(data, data->materials[i].extensions, data->materials[i].extensions_count);
cgltf_free_extras(data, &data->materials[i].extras);
}

data->memory.free_func(data->memory.user_data, data->materials);
Expand All @@ -1935,22 +1954,27 @@ void cgltf_free(cgltf_data* data)
data->memory.free_func(data->memory.user_data, data->images[i].mime_type);

cgltf_free_extensions(data, data->images[i].extensions, data->images[i].extensions_count);
cgltf_free_extras(data, &data->images[i].extras);
}

data->memory.free_func(data->memory.user_data, data->images);

for (cgltf_size i = 0; i < data->textures_count; ++i)
{
data->memory.free_func(data->memory.user_data, data->textures[i].name);

cgltf_free_extensions(data, data->textures[i].extensions, data->textures[i].extensions_count);
cgltf_free_extras(data, &data->textures[i].extras);
}

data->memory.free_func(data->memory.user_data, data->textures);

for (cgltf_size i = 0; i < data->samplers_count; ++i)
{
data->memory.free_func(data->memory.user_data, data->samplers[i].name);

cgltf_free_extensions(data, data->samplers[i].extensions, data->samplers[i].extensions_count);
cgltf_free_extras(data, &data->samplers[i].extras);
}

data->memory.free_func(data->memory.user_data, data->samplers);
Expand All @@ -1961,21 +1985,35 @@ void cgltf_free(cgltf_data* data)
data->memory.free_func(data->memory.user_data, data->skins[i].joints);

cgltf_free_extensions(data, data->skins[i].extensions, data->skins[i].extensions_count);
cgltf_free_extras(data, &data->skins[i].extras);
}

data->memory.free_func(data->memory.user_data, data->skins);

for (cgltf_size i = 0; i < data->cameras_count; ++i)
{
data->memory.free_func(data->memory.user_data, data->cameras[i].name);

if (data->cameras[i].type == cgltf_camera_type_perspective)
{
cgltf_free_extras(data, &data->cameras[i].data.perspective.extras);
}
else if (data->cameras[i].type == cgltf_camera_type_orthographic)
{
cgltf_free_extras(data, &data->cameras[i].data.orthographic.extras);
}

cgltf_free_extensions(data, data->cameras[i].extensions, data->cameras[i].extensions_count);
cgltf_free_extras(data, &data->cameras[i].extras);
}

data->memory.free_func(data->memory.user_data, data->cameras);

for (cgltf_size i = 0; i < data->lights_count; ++i)
{
data->memory.free_func(data->memory.user_data, data->lights[i].name);

cgltf_free_extras(data, &data->lights[i].extras);
}

data->memory.free_func(data->memory.user_data, data->lights);
Expand All @@ -1997,6 +2035,7 @@ void cgltf_free(cgltf_data* data)
}

cgltf_free_extensions(data, data->nodes[i].extensions, data->nodes[i].extensions_count);
cgltf_free_extras(data, &data->nodes[i].extras);
}

data->memory.free_func(data->memory.user_data, data->nodes);
Expand All @@ -2007,6 +2046,7 @@ void cgltf_free(cgltf_data* data)
data->memory.free_func(data->memory.user_data, data->scenes[i].nodes);

cgltf_free_extensions(data, data->scenes[i].extensions, data->scenes[i].extensions_count);
cgltf_free_extras(data, &data->scenes[i].extras);
}

data->memory.free_func(data->memory.user_data, data->scenes);
Expand All @@ -2017,28 +2057,34 @@ void cgltf_free(cgltf_data* data)
for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j)
{
cgltf_free_extensions(data, data->animations[i].samplers[j].extensions, data->animations[i].samplers[j].extensions_count);
cgltf_free_extras(data, &data->animations[i].samplers[j].extras);
}
data->memory.free_func(data->memory.user_data, data->animations[i].samplers);

for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j)
{
cgltf_free_extensions(data, data->animations[i].channels[j].extensions, data->animations[i].channels[j].extensions_count);
cgltf_free_extras(data, &data->animations[i].channels[j].extras);
}
data->memory.free_func(data->memory.user_data, data->animations[i].channels);

cgltf_free_extensions(data, data->animations[i].extensions, data->animations[i].extensions_count);
cgltf_free_extras(data, &data->animations[i].extras);
}

data->memory.free_func(data->memory.user_data, data->animations);

for (cgltf_size i = 0; i < data->variants_count; ++i)
{
data->memory.free_func(data->memory.user_data, data->variants[i].name);

cgltf_free_extras(data, &data->variants[i].extras);
}

data->memory.free_func(data->memory.user_data, data->variants);

cgltf_free_extensions(data, data->data_extensions, data->data_extensions_count);
cgltf_free_extras(data, &data->extras);

for (cgltf_size i = 0; i < data->extensions_used_count; ++i)
{
Expand Down Expand Up @@ -2702,10 +2748,25 @@ static int cgltf_parse_json_attribute_list(cgltf_options* options, jsmntok_t con

static int cgltf_parse_json_extras(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extras* out_extras)
{
(void)options;
(void)json_chunk;
if (out_extras->data)
{
return CGLTF_ERROR_JSON;
}

/* fill deprecated fields for now, this will be removed in the future */
out_extras->start_offset = tokens[i].start;
out_extras->end_offset = tokens[i].end;

size_t start = tokens[i].start;
size_t size = tokens[i].end - start;
out_extras->data = (char*)options->memory.alloc_func(options->memory.user_data, size + 1);
if (!out_extras->data)
{
return CGLTF_ERROR_NOMEM;
}
strncpy(out_extras->data, (const char*)json_chunk + start, size);
out_extras->data[size] = '\0';

i = cgltf_skip_json(tokens, i);
return i;
}
Expand Down Expand Up @@ -2860,7 +2921,7 @@ static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmnto

int material = -1;
int variants_tok = -1;
cgltf_extras extras = {0, 0};
int extras_tok = -1;

for (int k = 0; k < obj_size; ++k)
{
Expand All @@ -2881,7 +2942,8 @@ static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmnto
}
else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0)
{
i = cgltf_parse_json_extras(options, tokens, i + 1, json_chunk, &extras);
extras_tok = i + 1;
i = cgltf_skip_json(tokens, extras_tok);
}
else
{
Expand Down Expand Up @@ -2909,7 +2971,13 @@ static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmnto

out_mappings[*offset].material = CGLTF_PTRINDEX(cgltf_material, material);
out_mappings[*offset].variant = variant;
out_mappings[*offset].extras = extras;

if (extras_tok >= 0)
{
int e = cgltf_parse_json_extras(options, tokens, extras_tok, json_chunk, &out_mappings[*offset].extras);
if (e < 0)
return e;
}

(*offset)++;
}
Expand Down
24 changes: 14 additions & 10 deletions cgltf_write.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@
* buffer. Returns the number of bytes written to `buffer`, including a null
* terminator. If buffer is null, returns the number of bytes that would have
* been written. `data` is not deallocated.
*
* To write custom JSON into the `extras` field, aggregate all the custom JSON
* into a single buffer, then set `json` to this buffer. By supplying
* start_offset and end_offset values for various objects, you can select a
* range of characters within the aggregated buffer.
*/
#ifndef CGLTF_WRITE_H_INCLUDED__
#define CGLTF_WRITE_H_INCLUDED__
Expand Down Expand Up @@ -221,15 +216,24 @@ static void cgltf_write_strprop(cgltf_write_context* context, const char* label,

static void cgltf_write_extras(cgltf_write_context* context, const cgltf_extras* extras)
{
cgltf_size length = extras->end_offset - extras->start_offset;
if (length > 0 && context->data->json)
if (extras->data)
{
char* json_string = ((char*) context->data->json) + extras->start_offset;
cgltf_write_indent(context);
CGLTF_SPRINTF("%s", "\"extras\": ");
CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string);
CGLTF_SPRINTF("\"extras\": %s", extras->data);
context->needs_comma = 1;
}
else
{
cgltf_size length = extras->end_offset - extras->start_offset;
if (length > 0 && context->data->json)
{
char* json_string = ((char*) context->data->json) + extras->start_offset;
cgltf_write_indent(context);
CGLTF_SPRINTF("%s", "\"extras\": ");
CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string);
context->needs_comma = 1;
}
}
}

static void cgltf_write_stritem(cgltf_write_context* context, const char* item)
Expand Down

0 comments on commit 567663a

Please sign in to comment.