#pragma once //////////////////////////////////////////////////////////////////////////////// // The MIT License (MIT) // // Copyright (c) 2020 Nicholas Frechette & Animation Compression Library contributors // // 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 // 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 // SOFTWARE. //////////////////////////////////////////////////////////////////////////////// // Included only once from compress.h #include "acl/core/buffer_tag.h" #include "acl/core/compressed_tracks.h" #include "acl/core/compressed_tracks_version.h" #include "acl/core/error.h" #include "acl/core/error_result.h" #include "acl/core/floating_point_exceptions.h" #include "acl/core/iallocator.h" #include "acl/core/scope_profiler.h" #include "acl/compression/compression_settings.h" #include "acl/compression/output_stats.h" #include "acl/compression/track_array.h" #include "acl/compression/impl/clip_context.h" #include "acl/compression/impl/constant_track_impl.h" #include "acl/compression/impl/normalize_track_impl.h" #include "acl/compression/impl/quantize_track_impl.h" #include "acl/compression/impl/track_list_context.h" #include "acl/compression/impl/track_range_impl.h" #include "acl/compression/impl/write_compression_stats_impl.h" #include "acl/compression/impl/write_track_data_impl.h" #include "acl/compression/impl/track_stream.h" #include "acl/compression/impl/convert_rotation_streams.h" #include "acl/compression/impl/compact_constant_streams.h" #include "acl/compression/impl/normalize_streams.h" #include "acl/compression/impl/quantize_streams.h" #include "acl/compression/impl/segment_streams.h" #include "acl/compression/impl/write_segment_data.h" #include "acl/compression/impl/write_stats.h" #include "acl/compression/impl/write_stream_bitsets.h" #include "acl/compression/impl/write_stream_data.h" #include "acl/compression/impl/write_track_metadata.h" #include namespace acl { namespace acl_impl { inline error_result compress_scalar_track_list(iallocator& allocator, const track_array& track_list, const compression_settings& settings, compressed_tracks*& out_compressed_tracks, output_stats& out_stats) { (void)out_stats; #if defined(SJSON_CPP_WRITER) scope_profiler compression_time; #endif track_list_context context; if (!initialize_context(allocator, track_list, context)) return error_result("Some samples are not finite"); extract_track_ranges(context); extract_constant_tracks(context); normalize_tracks(context); quantize_tracks(context); // Done transforming our input tracks, time to pack them into their final form const uint32_t per_track_metadata_size = write_track_metadata(context, nullptr); const uint32_t constant_values_size = write_track_constant_values(context, nullptr); const uint32_t range_values_size = write_track_range_values(context, nullptr); const uint32_t animated_num_bits = write_track_animated_values(context, nullptr); const uint32_t animated_values_size = (animated_num_bits + 7) / 8; // Round up to nearest byte const uint32_t num_bits_per_frame = context.num_samples != 0 ? (animated_num_bits / context.num_samples) : 0; uint32_t buffer_size = 0; buffer_size += sizeof(raw_buffer_header); // Header buffer_size += sizeof(tracks_header); // Header buffer_size += sizeof(scalar_tracks_header); // Header ACL_ASSERT(is_aligned_to(buffer_size, alignof(track_metadata)), "Invalid alignment"); buffer_size += per_track_metadata_size; // Per track metadata buffer_size = align_to(buffer_size, 4); // Align constant values buffer_size += constant_values_size; // Constant values ACL_ASSERT(is_aligned_to(buffer_size, 4), "Invalid alignment"); buffer_size += range_values_size; // Range values ACL_ASSERT(is_aligned_to(buffer_size, 4), "Invalid alignment"); buffer_size += animated_values_size; // Animated values // Optional metadata const uint32_t metadata_start_offset = align_to(buffer_size, 4); const uint32_t metadata_track_list_name_size = settings.include_track_list_name ? write_track_list_name(track_list, nullptr) : 0; const uint32_t metadata_track_names_size = settings.include_track_names ? write_track_names(track_list, context.track_output_indices, context.num_output_tracks, nullptr) : 0; const uint32_t metadata_track_descriptions_size = settings.include_track_descriptions ? write_track_descriptions(track_list, context.track_output_indices, context.num_output_tracks, nullptr) : 0; uint32_t metadata_size = 0; metadata_size += metadata_track_list_name_size; metadata_size = align_to(metadata_size, 4); metadata_size += metadata_track_names_size; metadata_size = align_to(metadata_size, 4); metadata_size += metadata_track_descriptions_size; if (metadata_size != 0) { buffer_size = align_to(buffer_size, 4); buffer_size += metadata_size; buffer_size = align_to(buffer_size, 4); buffer_size += sizeof(optional_metadata_header); } else buffer_size += 15; // Ensure we have sufficient padding for unaligned 16 byte loads uint8_t* buffer = allocate_type_array_aligned(allocator, buffer_size, alignof(compressed_tracks)); std::memset(buffer, 0, buffer_size); uint8_t* buffer_start = buffer; out_compressed_tracks = reinterpret_cast(buffer); raw_buffer_header* buffer_header = safe_ptr_cast(buffer); buffer += sizeof(raw_buffer_header); tracks_header* header = safe_ptr_cast(buffer); buffer += sizeof(tracks_header); // Write our primary header header->tag = static_cast(buffer_tag32::compressed_tracks); header->version = compressed_tracks_version16::latest; header->algorithm_type = algorithm_type8::uniformly_sampled; header->track_type = track_list.get_track_type(); header->num_tracks = context.num_tracks; header->num_samples = context.num_samples; header->sample_rate = context.sample_rate; header->set_has_metadata(metadata_size != 0); // Write our scalar tracks header scalar_tracks_header* scalars_header = safe_ptr_cast(buffer); buffer += sizeof(scalar_tracks_header); scalars_header->num_bits_per_frame = num_bits_per_frame; const uint8_t* packed_data_start_offset = buffer - sizeof(scalar_tracks_header); // Relative to our header scalars_header->metadata_per_track = buffer - packed_data_start_offset; buffer += per_track_metadata_size; buffer = align_to(buffer, 4); scalars_header->track_constant_values = buffer - packed_data_start_offset; buffer += constant_values_size; scalars_header->track_range_values = buffer - packed_data_start_offset; buffer += range_values_size; scalars_header->track_animated_values = buffer - packed_data_start_offset; buffer += animated_values_size; if (metadata_size != 0) { buffer = align_to(buffer, 4); buffer += metadata_size; buffer = align_to(buffer, 4); buffer += sizeof(optional_metadata_header); } else buffer += 15; (void)buffer_start; // Avoid VS2017 bug, it falsely reports this variable as unused even when asserts are enabled ACL_ASSERT((buffer_start + buffer_size) == buffer, "Buffer size and pointer mismatch"); // Write our compressed data track_metadata* per_track_metadata = scalars_header->get_track_metadata(); write_track_metadata(context, per_track_metadata); float* constant_values = scalars_header->get_track_constant_values(); write_track_constant_values(context, constant_values); float* range_values = scalars_header->get_track_range_values(); write_track_range_values(context, range_values); uint8_t* animated_values = scalars_header->get_track_animated_values(); write_track_animated_values(context, animated_values); // Optional metadata header is last uint32_t writter_metadata_track_list_name_size = 0; uint32_t written_metadata_track_names_size = 0; uint32_t written_metadata_track_descriptions_size = 0; if (metadata_size != 0) { optional_metadata_header* metadada_header = reinterpret_cast(buffer_start + buffer_size - sizeof(optional_metadata_header)); uint32_t metadata_offset = metadata_start_offset; if (settings.include_track_list_name) { metadada_header->track_list_name = metadata_offset; writter_metadata_track_list_name_size = write_track_list_name(track_list, metadada_header->get_track_list_name(*out_compressed_tracks)); metadata_offset += writter_metadata_track_list_name_size; } else metadada_header->track_list_name = invalid_ptr_offset(); if (settings.include_track_names) { metadata_offset = align_to(metadata_offset, 4); metadada_header->track_name_offsets = metadata_offset; written_metadata_track_names_size = write_track_names(track_list, context.track_output_indices, context.num_output_tracks, metadada_header->get_track_name_offsets(*out_compressed_tracks)); metadata_offset += written_metadata_track_names_size; } else metadada_header->track_name_offsets = invalid_ptr_offset(); metadada_header->parent_track_indices = invalid_ptr_offset(); // Not supported for scalar tracks if (settings.include_track_descriptions) { metadata_offset = align_to(metadata_offset, 4); metadada_header->track_descriptions = metadata_offset; written_metadata_track_descriptions_size = write_track_descriptions(track_list, context.track_output_indices, context.num_output_tracks, metadada_header->get_track_descriptions(*out_compressed_tracks)); metadata_offset += written_metadata_track_descriptions_size; } else metadada_header->track_descriptions = invalid_ptr_offset(); } ACL_ASSERT(writter_metadata_track_list_name_size == metadata_track_list_name_size, "Wrote too little or too much data"); ACL_ASSERT(written_metadata_track_names_size == metadata_track_names_size, "Wrote too little or too much data"); ACL_ASSERT(written_metadata_track_descriptions_size == metadata_track_descriptions_size, "Wrote too little or too much data"); // Finish the raw buffer header buffer_header->size = buffer_size; buffer_header->hash = hash32(safe_ptr_cast(header), buffer_size - sizeof(raw_buffer_header)); // Hash everything but the raw buffer header #if defined(ACL_HAS_ASSERT_CHECKS) if (metadata_size == 0) { for (const uint8_t* padding = buffer - 15; padding < buffer; ++padding) ACL_ASSERT(*padding == 0, "Padding was overwritten"); } #endif #if defined(SJSON_CPP_WRITER) compression_time.stop(); if (out_stats.logging != stat_logging::none) write_compression_stats(context, *out_compressed_tracks, compression_time, out_stats); #endif return error_result(); } inline error_result compress_transform_track_list(iallocator& allocator, const track_array_qvvf& track_list, compression_settings settings, const track_array_qvvf* additive_base_track_list, additive_clip_format8 additive_format, compressed_tracks*& out_compressed_tracks, output_stats& out_stats) { error_result result = settings.is_valid(); if (result.any()) return result; #if defined(SJSON_CPP_WRITER) scope_profiler compression_time; #endif // If every track is retains full precision, we disable segmenting since it provides no benefit if (!is_rotation_format_variable(settings.rotation_format) && !is_vector_format_variable(settings.translation_format) && !is_vector_format_variable(settings.scale_format)) { settings.segmenting.ideal_num_samples = 0xFFFFFFFF; settings.segmenting.max_num_samples = 0xFFFFFFFF; } // If we want the optional track descriptions, make sure to include the parent track indices if (settings.include_track_descriptions) settings.include_parent_track_indices = true; // Variable bit rate tracks need range reduction // Full precision tracks do not need range reduction since samples are stored raw range_reduction_flags8 range_reduction = range_reduction_flags8::none; if (is_rotation_format_variable(settings.rotation_format)) range_reduction |= range_reduction_flags8::rotations; if (is_vector_format_variable(settings.translation_format)) range_reduction |= range_reduction_flags8::translations; if (is_vector_format_variable(settings.scale_format)) range_reduction |= range_reduction_flags8::scales; // If we have no additive base, our additive format is always none if (additive_base_track_list == nullptr || additive_base_track_list->is_empty()) additive_format = additive_clip_format8::none; clip_context raw_clip_context; if (!initialize_clip_context(allocator, track_list, settings, additive_format, raw_clip_context)) return error_result("Some samples are not finite"); clip_context lossy_clip_context; initialize_clip_context(allocator, track_list, settings, additive_format, lossy_clip_context); const bool is_additive = additive_format != additive_clip_format8::none; clip_context additive_base_clip_context; if (is_additive && !initialize_clip_context(allocator, *additive_base_track_list, settings, additive_format, additive_base_clip_context)) return error_result("Some base samples are not finite"); // Convert our rotations if we need to convert_rotation_streams(allocator, lossy_clip_context, settings.rotation_format); // Extract our clip ranges now, we need it for compacting the constant streams extract_clip_bone_ranges(allocator, lossy_clip_context); // Compact and collapse the constant streams compact_constant_streams(allocator, lossy_clip_context, track_list, settings); uint32_t clip_range_data_size = 0; if (range_reduction != range_reduction_flags8::none) { // Normalize our samples into the clip wide ranges per bone normalize_clip_streams(lossy_clip_context, range_reduction); clip_range_data_size = get_stream_range_data_size(lossy_clip_context, range_reduction, settings.rotation_format); } segment_streams(allocator, lossy_clip_context, settings.segmenting); // If we have a single segment, skip segment range reduction since it won't help if (range_reduction != range_reduction_flags8::none && lossy_clip_context.num_segments > 1) { // Extract and fixup our segment wide ranges per bone extract_segment_bone_ranges(allocator, lossy_clip_context); // Normalize our samples into the segment wide ranges per bone normalize_segment_streams(lossy_clip_context, range_reduction); } quantize_streams(allocator, lossy_clip_context, settings, raw_clip_context, additive_base_clip_context, out_stats); uint32_t num_output_bones = 0; uint32_t* output_bone_mapping = create_output_track_mapping(allocator, track_list, num_output_bones); const uint32_t constant_data_size = get_constant_data_size(lossy_clip_context); calculate_animated_data_size(lossy_clip_context, output_bone_mapping, num_output_bones); uint32_t num_animated_variable_sub_tracks_padded = 0; const uint32_t format_per_track_data_size = get_format_per_track_data_size(lossy_clip_context, settings.rotation_format, settings.translation_format, settings.scale_format, &num_animated_variable_sub_tracks_padded); auto animated_group_filter_action = [&](animation_track_type8 group_type, uint32_t bone_index) { const BoneStreams& bone_stream = lossy_clip_context.segments[0].bone_streams[bone_index]; if (group_type == animation_track_type8::rotation) return !bone_stream.is_rotation_constant; else if (group_type == animation_track_type8::translation) return !bone_stream.is_translation_constant; else return !bone_stream.is_scale_constant; }; uint32_t num_animated_groups = 0; animation_track_type8* animated_sub_track_groups = calculate_sub_track_groups(lossy_clip_context.segments[0], output_bone_mapping, num_output_bones, num_animated_groups, animated_group_filter_action); const uint32_t animated_group_types_size = sizeof(animation_track_type8) * (num_animated_groups + 1); // Includes terminator const uint32_t num_tracks_per_bone = lossy_clip_context.has_scale ? 3 : 2; const uint32_t num_tracks = uint32_t(num_output_bones) * num_tracks_per_bone; const bitset_description bitset_desc = bitset_description::make_from_num_bits(num_tracks); // Adding an extra index at the end to delimit things, the index is always invalid: 0xFFFFFFFF const uint32_t segment_start_indices_size = lossy_clip_context.num_segments > 1 ? (sizeof(uint32_t) * (lossy_clip_context.num_segments + 1)) : 0; const uint32_t segment_headers_size = sizeof(segment_header) * lossy_clip_context.num_segments; uint32_t buffer_size = 0; // Per clip data buffer_size += sizeof(raw_buffer_header); // Header buffer_size += sizeof(tracks_header); // Header buffer_size += sizeof(transform_tracks_header); // Header const uint32_t clip_header_size = buffer_size; buffer_size = align_to(buffer_size, 4); // Align segment start indices buffer_size += segment_start_indices_size; // Segment start indices buffer_size = align_to(buffer_size, 4); // Align segment headers buffer_size += segment_headers_size; // Segment headers buffer_size = align_to(buffer_size, 4); // Align bitsets const uint32_t clip_segment_header_size = buffer_size - clip_header_size; buffer_size += bitset_desc.get_num_bytes(); // Default tracks bitset buffer_size += bitset_desc.get_num_bytes(); // Constant tracks bitset buffer_size = align_to(buffer_size, 4); // Align constant track data buffer_size += constant_data_size; // Constant track data buffer_size = align_to(buffer_size, 4); // Align range data buffer_size += clip_range_data_size; // Range data buffer_size += animated_group_types_size; // Our animated group types const uint32_t clip_data_size = buffer_size - clip_segment_header_size - clip_header_size; if (are_all_enum_flags_set(out_stats.logging, stat_logging::detailed)) { constexpr uint32_t k_cache_line_byte_size = 64; lossy_clip_context.decomp_touched_bytes = clip_header_size + clip_data_size; lossy_clip_context.decomp_touched_bytes += sizeof(uint32_t) * 4; // We touch at most 4 segment start indices lossy_clip_context.decomp_touched_bytes += sizeof(segment_header) * 2; // We touch at most 2 segment headers lossy_clip_context.decomp_touched_cache_lines = align_to(clip_header_size, k_cache_line_byte_size) / k_cache_line_byte_size; lossy_clip_context.decomp_touched_cache_lines += align_to(clip_data_size, k_cache_line_byte_size) / k_cache_line_byte_size; lossy_clip_context.decomp_touched_cache_lines += 1; // All 4 segment start indices should fit in a cache line lossy_clip_context.decomp_touched_cache_lines += 1; // Both segment headers should fit in a cache line } // Per segment data for (SegmentContext& segment : lossy_clip_context.segment_iterator()) { const uint32_t header_start = buffer_size; buffer_size += format_per_track_data_size; // Format per track data // TODO: Alignment only necessary with 16bit per component (segment constant tracks), need to fix scalar decoding path buffer_size = align_to(buffer_size, 2); // Align range data buffer_size += segment.range_data_size; // Range data const uint32_t header_end = buffer_size; // TODO: Variable bit rate doesn't need alignment buffer_size = align_to(buffer_size, 4); // Align animated data buffer_size += segment.animated_data_size; // Animated track data segment.segment_data_size = buffer_size - header_start; segment.total_header_size = header_end - header_start; } const uint32_t segment_data_size = buffer_size - clip_data_size - clip_segment_header_size - clip_header_size; // Optional metadata const uint32_t metadata_start_offset = align_to(buffer_size, 4); const uint32_t metadata_track_list_name_size = settings.include_track_list_name ? write_track_list_name(track_list, nullptr) : 0; const uint32_t metadata_track_names_size = settings.include_track_names ? write_track_names(track_list, output_bone_mapping, num_output_bones, nullptr) : 0; const uint32_t metadata_parent_track_indices_size = settings.include_parent_track_indices ? write_parent_track_indices(track_list, output_bone_mapping, num_output_bones, nullptr) : 0; const uint32_t metadata_track_descriptions_size = settings.include_track_descriptions ? write_track_descriptions(track_list, output_bone_mapping, num_output_bones, nullptr) : 0; uint32_t metadata_size = 0; metadata_size += metadata_track_list_name_size; metadata_size = align_to(metadata_size, 4); metadata_size += metadata_track_names_size; metadata_size = align_to(metadata_size, 4); metadata_size += metadata_parent_track_indices_size; metadata_size = align_to(metadata_size, 4); metadata_size += metadata_track_descriptions_size; if (metadata_size != 0) { buffer_size = align_to(buffer_size, 4); buffer_size += metadata_size; buffer_size = align_to(buffer_size, 4); buffer_size += sizeof(optional_metadata_header); } else buffer_size += 15; // Ensure we have sufficient padding for unaligned 16 byte loads uint8_t* buffer = allocate_type_array_aligned(allocator, buffer_size, alignof(compressed_tracks)); std::memset(buffer, 0, buffer_size); uint8_t* buffer_start = buffer; out_compressed_tracks = reinterpret_cast(buffer); raw_buffer_header* buffer_header = safe_ptr_cast(buffer); buffer += sizeof(raw_buffer_header); tracks_header* header = safe_ptr_cast(buffer); buffer += sizeof(tracks_header); // Write our primary header header->tag = static_cast(buffer_tag32::compressed_tracks); header->version = compressed_tracks_version16::latest; header->algorithm_type = algorithm_type8::uniformly_sampled; header->track_type = track_list.get_track_type(); header->num_tracks = num_output_bones; header->num_samples = track_list.get_num_samples_per_track(); header->sample_rate = track_list.get_sample_rate(); header->set_rotation_format(settings.rotation_format); header->set_translation_format(settings.translation_format); header->set_scale_format(settings.scale_format); header->set_has_scale(lossy_clip_context.has_scale); // Our default scale is 1.0 if we have no additive base or if we don't use 'additive1', otherwise it is 0.0 header->set_default_scale(!is_additive || additive_format != additive_clip_format8::additive1 ? 1 : 0); header->set_has_metadata(metadata_size != 0); // Write our transform tracks header transform_tracks_header* transforms_header = safe_ptr_cast(buffer); buffer += sizeof(transform_tracks_header); transforms_header->num_segments = lossy_clip_context.num_segments; transforms_header->num_animated_variable_sub_tracks = num_animated_variable_sub_tracks_padded; get_num_constant_samples(lossy_clip_context, transforms_header->num_constant_rotation_samples, transforms_header->num_constant_translation_samples, transforms_header->num_constant_scale_samples); get_num_animated_sub_tracks(lossy_clip_context.segments[0], transforms_header->num_animated_rotation_sub_tracks, transforms_header->num_animated_translation_sub_tracks, transforms_header->num_animated_scale_sub_tracks); const uint32_t segment_start_indices_offset = align_to(sizeof(transform_tracks_header), 4); // Relative to the start of our transform_tracks_header transforms_header->segment_headers_offset = align_to(segment_start_indices_offset + segment_start_indices_size, 4); transforms_header->default_tracks_bitset_offset = align_to(transforms_header->segment_headers_offset + segment_headers_size, 4); transforms_header->constant_tracks_bitset_offset = transforms_header->default_tracks_bitset_offset + bitset_desc.get_num_bytes(); transforms_header->constant_track_data_offset = align_to(transforms_header->constant_tracks_bitset_offset + bitset_desc.get_num_bytes(), 4); transforms_header->clip_range_data_offset = align_to(transforms_header->constant_track_data_offset + constant_data_size, 4); transforms_header->animated_group_types_offset = transforms_header->clip_range_data_offset + clip_range_data_size; uint32_t written_segment_start_indices_size = 0; if (lossy_clip_context.num_segments > 1) written_segment_start_indices_size = write_segment_start_indices(lossy_clip_context, transforms_header->get_segment_start_indices()); const uint32_t segment_data_start_offset = transforms_header->animated_group_types_offset + animated_group_types_size; const uint32_t written_segment_headers_size = write_segment_headers(lossy_clip_context, settings, transforms_header->get_segment_headers(), segment_data_start_offset); uint32_t written_bitset_size = 0; written_bitset_size += write_default_track_bitset(lossy_clip_context, transforms_header->get_default_tracks_bitset(), bitset_desc, output_bone_mapping, num_output_bones); written_bitset_size += write_constant_track_bitset(lossy_clip_context, transforms_header->get_constant_tracks_bitset(), bitset_desc, output_bone_mapping, num_output_bones); uint32_t written_constant_data_size = 0; if (constant_data_size != 0) written_constant_data_size = write_constant_track_data(lossy_clip_context, settings.rotation_format, transforms_header->get_constant_track_data(), constant_data_size, output_bone_mapping, num_output_bones); else transforms_header->constant_track_data_offset = invalid_ptr_offset(); uint32_t written_clip_range_data_size = 0; if (range_reduction != range_reduction_flags8::none) written_clip_range_data_size = write_clip_range_data(lossy_clip_context, range_reduction, transforms_header->get_clip_range_data(), clip_range_data_size, output_bone_mapping, num_output_bones); else transforms_header->clip_range_data_offset = invalid_ptr_offset(); const uint32_t written_animated_group_types_size = write_animated_group_types(animated_sub_track_groups, num_animated_groups, transforms_header->get_animated_group_types(), animated_group_types_size); const uint32_t written_segment_data_size = write_segment_data(lossy_clip_context, settings, range_reduction, *transforms_header, output_bone_mapping, num_output_bones); // Optional metadata header is last uint32_t writter_metadata_track_list_name_size = 0; uint32_t written_metadata_track_names_size = 0; uint32_t written_metadata_parent_track_indices_size = 0; uint32_t written_metadata_track_descriptions_size = 0; if (metadata_size != 0) { optional_metadata_header* metadada_header = reinterpret_cast(buffer_start + buffer_size - sizeof(optional_metadata_header)); uint32_t metadata_offset = metadata_start_offset; // Relative to the start of our compressed_tracks if (settings.include_track_list_name) { metadada_header->track_list_name = metadata_offset; writter_metadata_track_list_name_size = write_track_list_name(track_list, metadada_header->get_track_list_name(*out_compressed_tracks)); metadata_offset += writter_metadata_track_list_name_size; } else metadada_header->track_list_name = invalid_ptr_offset(); if (settings.include_track_names) { metadata_offset = align_to(metadata_offset, 4); metadada_header->track_name_offsets = metadata_offset; written_metadata_track_names_size = write_track_names(track_list, output_bone_mapping, num_output_bones, metadada_header->get_track_name_offsets(*out_compressed_tracks)); metadata_offset += written_metadata_track_names_size; } else metadada_header->track_name_offsets = invalid_ptr_offset(); if (settings.include_parent_track_indices) { metadata_offset = align_to(metadata_offset, 4); metadada_header->parent_track_indices = metadata_offset; written_metadata_parent_track_indices_size = write_parent_track_indices(track_list, output_bone_mapping, num_output_bones, metadada_header->get_parent_track_indices(*out_compressed_tracks)); metadata_offset += written_metadata_parent_track_indices_size; } else metadada_header->parent_track_indices = invalid_ptr_offset(); if (settings.include_track_descriptions) { metadata_offset = align_to(metadata_offset, 4); metadada_header->track_descriptions = metadata_offset; written_metadata_track_descriptions_size = write_track_descriptions(track_list, output_bone_mapping, num_output_bones, metadada_header->get_track_descriptions(*out_compressed_tracks)); metadata_offset += written_metadata_track_descriptions_size; } else metadada_header->track_descriptions = invalid_ptr_offset(); } #if defined(ACL_HAS_ASSERT_CHECKS) { // Make sure we wrote the right amount of data buffer = align_to(buffer, 4); // Align segment start indices buffer += written_segment_start_indices_size; buffer = align_to(buffer, 4); // Align segment headers buffer += written_segment_headers_size; buffer = align_to(buffer, 4); // Align bitsets buffer += written_bitset_size; buffer = align_to(buffer, 4); // Align constant track data buffer += written_constant_data_size; buffer = align_to(buffer, 4); // Align range data buffer += written_clip_range_data_size; buffer += written_animated_group_types_size; buffer += written_segment_data_size; if (metadata_size != 0) { buffer = align_to(buffer, 4); buffer += metadata_size; buffer = align_to(buffer, 4); buffer += sizeof(optional_metadata_header); } else buffer += 15; // Ensure we have sufficient padding for unaligned 16 byte loads (void)buffer_start; // Avoid VS2017 bug, it falsely reports this variable as unused even when asserts are enabled ACL_ASSERT(written_segment_start_indices_size == segment_start_indices_size, "Wrote too little or too much data"); ACL_ASSERT(written_segment_headers_size == segment_headers_size, "Wrote too little or too much data"); ACL_ASSERT(written_segment_data_size == segment_data_size, "Wrote too little or too much data"); ACL_ASSERT(written_bitset_size == (bitset_desc.get_num_bytes() * 2), "Wrote too little or too much data"); ACL_ASSERT(written_constant_data_size == constant_data_size, "Wrote too little or too much data"); ACL_ASSERT(written_clip_range_data_size == clip_range_data_size, "Wrote too little or too much data"); ACL_ASSERT(written_animated_group_types_size == animated_group_types_size, "Wrote too little or too much data"); ACL_ASSERT(writter_metadata_track_list_name_size == metadata_track_list_name_size, "Wrote too little or too much data"); ACL_ASSERT(written_metadata_track_names_size == metadata_track_names_size, "Wrote too little or too much data"); ACL_ASSERT(written_metadata_parent_track_indices_size == metadata_parent_track_indices_size, "Wrote too little or too much data"); ACL_ASSERT(written_metadata_track_descriptions_size == metadata_track_descriptions_size, "Wrote too little or too much data"); ACL_ASSERT(uint32_t(buffer - buffer_start) == buffer_size, "Wrote too little or too much data"); if (metadata_size == 0) { for (const uint8_t* padding = buffer - 15; padding < buffer; ++padding) ACL_ASSERT(*padding == 0, "Padding was overwritten"); } } #else (void)written_segment_start_indices_size; (void)written_segment_headers_size; (void)written_bitset_size; (void)written_constant_data_size; (void)written_clip_range_data_size; (void)written_segment_data_size; (void)written_animated_group_types_size; (void)segment_data_size; (void)buffer_start; #endif // Finish the raw buffer header buffer_header->size = buffer_size; buffer_header->hash = hash32(safe_ptr_cast(header), buffer_size - sizeof(raw_buffer_header)); // Hash everything but the raw buffer header #if defined(SJSON_CPP_WRITER) compression_time.stop(); if (out_stats.logging != stat_logging::none) write_stats(allocator, track_list, lossy_clip_context, *out_compressed_tracks, settings, range_reduction, raw_clip_context, additive_base_clip_context, compression_time, out_stats); #endif deallocate_type_array(allocator, animated_sub_track_groups, num_animated_groups); deallocate_type_array(allocator, output_bone_mapping, num_output_bones); destroy_clip_context(lossy_clip_context); destroy_clip_context(raw_clip_context); destroy_clip_context(additive_base_clip_context); return error_result(); } } inline error_result compress_track_list(iallocator& allocator, const track_array& track_list, const compression_settings& settings, compressed_tracks*& out_compressed_tracks, output_stats& out_stats) { using namespace acl_impl; error_result result = track_list.is_valid(); if (result.any()) return result; // Disable floating point exceptions during compression because we leverage all SIMD lanes // and we might intentionally divide by zero, etc. scope_disable_fp_exceptions fp_off; if (track_list.get_track_category() == track_category8::transformf) result = compress_transform_track_list(allocator, track_array_cast(track_list), settings, nullptr, additive_clip_format8::none, out_compressed_tracks, out_stats); else result = compress_scalar_track_list(allocator, track_list, settings, out_compressed_tracks, out_stats); return result; } inline error_result compress_track_list(iallocator& allocator, const track_array_qvvf& track_list, const compression_settings& settings, const track_array_qvvf& additive_base_track_list, additive_clip_format8 additive_format, compressed_tracks*& out_compressed_tracks, output_stats& out_stats) { using namespace acl_impl; error_result result = track_list.is_valid(); if (result.any()) return result; if (additive_format != additive_clip_format8::none) { result = additive_base_track_list.is_valid(); if (result.any()) return result; } // Disable floating point exceptions during compression because we leverage all SIMD lanes // and we might intentionally divide by zero, etc. scope_disable_fp_exceptions fp_off; return compress_transform_track_list(allocator, track_list, settings, &additive_base_track_list, additive_format, out_compressed_tracks, out_stats); } }