708 lines
35 KiB
C++
708 lines
35 KiB
C++
#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 <cstdint>
|
|
|
|
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<uint8_t>(allocator, buffer_size, alignof(compressed_tracks));
|
|
std::memset(buffer, 0, buffer_size);
|
|
|
|
uint8_t* buffer_start = buffer;
|
|
out_compressed_tracks = reinterpret_cast<compressed_tracks*>(buffer);
|
|
|
|
raw_buffer_header* buffer_header = safe_ptr_cast<raw_buffer_header>(buffer);
|
|
buffer += sizeof(raw_buffer_header);
|
|
|
|
tracks_header* header = safe_ptr_cast<tracks_header>(buffer);
|
|
buffer += sizeof(tracks_header);
|
|
|
|
// Write our primary header
|
|
header->tag = static_cast<uint32_t>(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<scalar_tracks_header>(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<optional_metadata_header*>(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<const uint8_t>(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<uint8_t>(allocator, buffer_size, alignof(compressed_tracks));
|
|
std::memset(buffer, 0, buffer_size);
|
|
|
|
uint8_t* buffer_start = buffer;
|
|
out_compressed_tracks = reinterpret_cast<compressed_tracks*>(buffer);
|
|
|
|
raw_buffer_header* buffer_header = safe_ptr_cast<raw_buffer_header>(buffer);
|
|
buffer += sizeof(raw_buffer_header);
|
|
|
|
tracks_header* header = safe_ptr_cast<tracks_header>(buffer);
|
|
buffer += sizeof(tracks_header);
|
|
|
|
// Write our primary header
|
|
header->tag = static_cast<uint32_t>(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<transform_tracks_header>(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<uint32_t>(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<optional_metadata_header*>(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<const uint8_t>(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_array_qvvf>(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);
|
|
}
|
|
}
|