#pragma once //////////////////////////////////////////////////////////////////////////////// // The MIT License (MIT) // // Copyright (c) 2017 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. //////////////////////////////////////////////////////////////////////////////// #if defined(SJSON_CPP_WRITER) #include "acl/core/track_formats.h" #include "acl/core/utils.h" #include "acl/core/variable_bit_rates.h" #include "acl/core/impl/compiler_utils.h" #include "acl/core/impl/memory_cache.h" #include "acl/compression/transform_error_metrics.h" #include "acl/compression/track_error.h" #include "acl/compression/impl/clip_context.h" #include #include #include #include ACL_IMPL_FILE_PRAGMA_PUSH namespace acl { namespace acl_impl { inline void write_summary_segment_stats(const SegmentContext& segment, rotation_format8 rotation_format, vector_format8 translation_format, vector_format8 scale_format, sjson::ObjectWriter& writer) { writer["segment_index"] = segment.segment_index; writer["num_samples"] = segment.num_samples; const uint32_t format_per_track_data_size = get_format_per_track_data_size(*segment.clip, rotation_format, translation_format, scale_format); uint32_t segment_size = 0; segment_size += format_per_track_data_size; // Format per track data segment_size = align_to(segment_size, 2); // Align range data segment_size += segment.range_data_size; // Range data segment_size = align_to(segment_size, 4); // Align animated data segment_size += segment.animated_data_size; // Animated track data writer["segment_size"] = segment_size; writer["animated_frame_size"] = double(segment.animated_data_size) / double(segment.num_samples); } inline void write_detailed_segment_stats(const SegmentContext& segment, sjson::ObjectWriter& writer) { uint32_t bit_rate_counts[k_num_bit_rates] = { 0 }; for (const BoneStreams& bone_stream : segment.const_bone_iterator()) { const uint8_t rotation_bit_rate = bone_stream.rotations.get_bit_rate(); if (rotation_bit_rate != k_invalid_bit_rate) bit_rate_counts[rotation_bit_rate]++; const uint8_t translation_bit_rate = bone_stream.translations.get_bit_rate(); if (translation_bit_rate != k_invalid_bit_rate) bit_rate_counts[translation_bit_rate]++; const uint8_t scale_bit_rate = bone_stream.scales.get_bit_rate(); if (scale_bit_rate != k_invalid_bit_rate) bit_rate_counts[scale_bit_rate]++; } writer["bit_rate_counts"] = [&](sjson::ArrayWriter& bitrate_writer) { for (uint32_t bit_rate = 0; bit_rate < k_num_bit_rates; ++bit_rate) bitrate_writer.push(bit_rate_counts[bit_rate]); }; // We assume that we always interpolate between 2 poses const uint32_t animated_pose_byte_size = align_to(segment.animated_pose_bit_size * 2, 8) / 8; constexpr uint32_t k_cache_line_byte_size = 64; const uint32_t num_segment_header_cache_lines = align_to(segment.total_header_size, k_cache_line_byte_size) / k_cache_line_byte_size; const uint32_t num_animated_pose_cache_lines = align_to(animated_pose_byte_size, k_cache_line_byte_size) / k_cache_line_byte_size; writer["decomp_touched_bytes"] = segment.clip->decomp_touched_bytes + segment.total_header_size + animated_pose_byte_size; writer["decomp_touched_cache_lines"] = segment.clip->decomp_touched_cache_lines + num_segment_header_cache_lines + num_animated_pose_cache_lines; } inline void write_exhaustive_segment_stats(iallocator& allocator, const SegmentContext& segment, const clip_context& raw_clip_context, const clip_context& additive_base_clip_context, const compression_settings& settings, const track_array_qvvf& track_list, sjson::ObjectWriter& writer) { const uint32_t num_bones = raw_clip_context.num_bones; const bool has_scale = segment_context_has_scale(segment); ACL_ASSERT(!settings.error_metric->needs_conversion(has_scale), "Error metric conversion not supported"); const auto local_to_object_space_impl = std::mem_fn(has_scale ? &itransform_error_metric::local_to_object_space : &itransform_error_metric::local_to_object_space_no_scale); const auto calculate_error_impl = std::mem_fn(has_scale ? &itransform_error_metric::calculate_error : &itransform_error_metric::calculate_error_no_scale); const auto apply_additive_to_base_impl = std::mem_fn(has_scale ? &itransform_error_metric::apply_additive_to_base : &itransform_error_metric::apply_additive_to_base_no_scale); rtm::qvvf* raw_local_pose = allocate_type_array(allocator, num_bones); rtm::qvvf* base_local_pose = allocate_type_array(allocator, num_bones); rtm::qvvf* lossy_local_pose = allocate_type_array(allocator, num_bones); rtm::qvvf* raw_object_pose = allocate_type_array(allocator, num_bones); rtm::qvvf* lossy_object_pose = allocate_type_array(allocator, num_bones); uint32_t* parent_transform_indices = allocate_type_array(allocator, num_bones); uint32_t* self_transform_indices = allocate_type_array(allocator, num_bones); for (uint32_t transform_index = 0; transform_index < num_bones; ++transform_index) { const track_qvvf& track = track_list[transform_index]; const track_desc_transformf& desc = track.get_description(); parent_transform_indices[transform_index] = desc.parent_index; self_transform_indices[transform_index] = transform_index; } const float sample_rate = raw_clip_context.sample_rate; const float ref_duration = calculate_duration(raw_clip_context.num_samples, sample_rate); itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_raw; apply_additive_to_base_args_raw.dirty_transform_indices = self_transform_indices; apply_additive_to_base_args_raw.num_dirty_transforms = num_bones; apply_additive_to_base_args_raw.local_transforms = raw_local_pose; apply_additive_to_base_args_raw.base_transforms = base_local_pose; apply_additive_to_base_args_raw.num_transforms = num_bones; itransform_error_metric::apply_additive_to_base_args apply_additive_to_base_args_lossy = apply_additive_to_base_args_raw; apply_additive_to_base_args_lossy.local_transforms = lossy_local_pose; itransform_error_metric::local_to_object_space_args local_to_object_space_args_raw; local_to_object_space_args_raw.dirty_transform_indices = self_transform_indices; local_to_object_space_args_raw.num_dirty_transforms = num_bones; local_to_object_space_args_raw.parent_transform_indices = parent_transform_indices; local_to_object_space_args_raw.local_transforms = raw_local_pose; local_to_object_space_args_raw.num_transforms = num_bones; itransform_error_metric::local_to_object_space_args local_to_object_space_args_lossy = local_to_object_space_args_raw; local_to_object_space_args_lossy.local_transforms = lossy_local_pose; track_error worst_bone_error; writer["error_per_frame_and_bone"] = [&](sjson::ArrayWriter& frames_writer) { for (uint32_t sample_index = 0; sample_index < segment.num_samples; ++sample_index) { const float sample_time = rtm::scalar_min(float(segment.clip_sample_offset + sample_index) / sample_rate, ref_duration); sample_streams(raw_clip_context.segments[0].bone_streams, num_bones, sample_time, raw_local_pose); sample_streams(segment.bone_streams, num_bones, sample_time, lossy_local_pose); if (raw_clip_context.has_additive_base) { const float normalized_sample_time = additive_base_clip_context.num_samples > 1 ? (sample_time / ref_duration) : 0.0F; const float additive_sample_time = additive_base_clip_context.num_samples > 1 ? (normalized_sample_time * additive_base_clip_context.duration) : 0.0F; sample_streams(additive_base_clip_context.segments[0].bone_streams, num_bones, additive_sample_time, base_local_pose); apply_additive_to_base_impl(settings.error_metric, apply_additive_to_base_args_raw, raw_local_pose); apply_additive_to_base_impl(settings.error_metric, apply_additive_to_base_args_lossy, lossy_local_pose); } local_to_object_space_impl(settings.error_metric, local_to_object_space_args_raw, raw_object_pose); local_to_object_space_impl(settings.error_metric, local_to_object_space_args_lossy, lossy_object_pose); frames_writer.push_newline(); frames_writer.push([&](sjson::ArrayWriter& frame_writer) { for (uint32_t bone_index = 0; bone_index < num_bones; ++bone_index) { const track_qvvf& track = track_list[bone_index]; const track_desc_transformf& desc = track.get_description(); itransform_error_metric::calculate_error_args calculate_error_args; calculate_error_args.transform0 = raw_object_pose + bone_index; calculate_error_args.transform1 = lossy_object_pose + bone_index; calculate_error_args.construct_sphere_shell(desc.shell_distance); const float error = rtm::scalar_cast(calculate_error_impl(settings.error_metric, calculate_error_args)); frame_writer.push(error); if (error > worst_bone_error.error) { worst_bone_error.error = error; worst_bone_error.index = bone_index; worst_bone_error.sample_time = sample_time; } } }); } }; writer["max_error"] = worst_bone_error.error; writer["worst_bone"] = worst_bone_error.index; writer["worst_time"] = worst_bone_error.sample_time; deallocate_type_array(allocator, raw_local_pose, num_bones); deallocate_type_array(allocator, base_local_pose, num_bones); deallocate_type_array(allocator, lossy_local_pose, num_bones); deallocate_type_array(allocator, raw_object_pose, num_bones); deallocate_type_array(allocator, lossy_object_pose, num_bones); deallocate_type_array(allocator, parent_transform_indices, num_bones); deallocate_type_array(allocator, self_transform_indices, num_bones); } inline uint32_t calculate_clip_metadata_common_size(const clip_context& clip, const compressed_tracks& compressed_clip) { uint32_t result = 0; // Segment start indices and headers result += clip.num_segments > 1 ? (sizeof(uint32_t) * (clip.num_segments + 1)) : 0; result += sizeof(segment_header) * clip.num_segments; // Default/constant track bit sets const bitset_description bitset_desc = bitset_description::make_from_num_bits(compressed_clip.get_num_tracks()); result += bitset_desc.get_num_bytes(); result += bitset_desc.get_num_bytes(); return result; } inline uint32_t calculate_segment_metadata_common_size(const clip_context& clip, const compression_settings& settings) { const bool is_rotation_variable = is_rotation_format_variable(settings.rotation_format); const bool is_translation_variable = is_vector_format_variable(settings.translation_format); const bool is_scale_variable = is_vector_format_variable(settings.scale_format); // Only use the first segment, it contains the necessary information const SegmentContext& segment = clip.segments[0]; uint32_t result = 0; for (const BoneStreams& bone_stream : segment.const_bone_iterator()) { if (bone_stream.is_stripped_from_output()) continue; // Format per track if (!bone_stream.is_rotation_constant && is_rotation_variable) result++; if (!bone_stream.is_translation_constant && is_translation_variable) result++; if (!bone_stream.is_scale_constant && is_scale_variable) result++; } return result * clip.num_segments; } inline uint32_t calculate_segment_metadata_rotation_size(const clip_context& clip, range_reduction_flags8 range_reduction) { if (clip.num_segments == 1) return 0; // Only use the first segment, it contains the necessary information const SegmentContext& segment = clip.segments[0]; uint32_t result = 0; for (const BoneStreams& bone_stream : segment.const_bone_iterator()) { if (bone_stream.is_stripped_from_output()) continue; // Range data if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations) && !bone_stream.is_rotation_constant) { const uint32_t num_components = bone_stream.rotations.get_rotation_format() == rotation_format8::quatf_full ? 8 : 6; result += num_components * k_segment_range_reduction_num_bytes_per_component; } } return result * clip.num_segments; } inline uint32_t calculate_segment_metadata_translation_size(const clip_context& clip, range_reduction_flags8 range_reduction) { if (clip.num_segments == 1) return 0; // Only use the first segment, it contains the necessary information const SegmentContext& segment = clip.segments[0]; uint32_t result = 0; for (const BoneStreams& bone_stream : segment.const_bone_iterator()) { if (bone_stream.is_stripped_from_output()) continue; // Range data if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations) && !bone_stream.is_translation_constant) result += k_segment_range_reduction_num_bytes_per_component * 6; } return result * clip.num_segments; } inline uint32_t calculate_segment_metadata_scale_size(const clip_context& clip, range_reduction_flags8 range_reduction) { if (clip.num_segments == 1) return 0; // Only use the first segment, it contains the necessary information const SegmentContext& segment = clip.segments[0]; uint32_t result = 0; for (const BoneStreams& bone_stream : segment.const_bone_iterator()) { if (bone_stream.is_stripped_from_output()) continue; // Range data if (are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales) && !bone_stream.is_scale_constant) result += k_segment_range_reduction_num_bytes_per_component * 6; } return result * clip.num_segments; } inline uint32_t calculate_segment_animated_rotation_size(const clip_context& clip) { uint32_t result = 0; for (const SegmentContext& segment : clip.segment_iterator()) result += align_to(segment.animated_pose_rotation_bit_size, 8) / 8; // Convert bits to bytes; return result; } inline uint32_t calculate_segment_animated_translation_size(const clip_context& clip) { uint32_t result = 0; for (const SegmentContext& segment : clip.segment_iterator()) result += align_to(segment.animated_pose_translation_bit_size, 8) / 8; // Convert bits to bytes; return result; } inline uint32_t calculate_segment_animated_scale_size(const clip_context& clip) { uint32_t result = 0; for (const SegmentContext& segment : clip.segment_iterator()) result += align_to(segment.animated_pose_scale_bit_size, 8) / 8; // Convert bits to bytes; return result; } inline uint32_t calculate_segment_animated_data_size(const clip_context& clip) { uint32_t result = 0; for (const SegmentContext& segment : clip.segment_iterator()) result += segment.animated_data_size; return result; } inline void write_stats(iallocator& allocator, const track_array_qvvf& track_list, const clip_context& clip, const compressed_tracks& compressed_clip, const compression_settings& settings, range_reduction_flags8 range_reduction, const clip_context& raw_clip, const clip_context& additive_base_clip_context, const scope_profiler& compression_time, output_stats& stats) { ACL_ASSERT(stats.writer != nullptr, "Attempted to log stats without a writer"); if (stats.writer == nullptr) return; const uint32_t raw_size = track_list.get_raw_size(); const uint32_t compressed_size = compressed_clip.get_size(); const double compression_ratio = double(raw_size) / double(compressed_size); sjson::ObjectWriter& writer = *stats.writer; writer["algorithm_name"] = get_algorithm_name(algorithm_type8::uniformly_sampled); writer["algorithm_uid"] = settings.get_hash(); writer["clip_name"] = track_list.get_name().c_str(); writer["raw_size"] = raw_size; writer["compressed_size"] = compressed_size; writer["compression_ratio"] = compression_ratio; writer["compression_time"] = compression_time.get_elapsed_seconds(); writer["duration"] = track_list.get_duration(); writer["num_samples"] = track_list.get_num_samples_per_track(); writer["num_bones"] = compressed_clip.get_num_tracks(); writer["rotation_format"] = get_rotation_format_name(settings.rotation_format); writer["translation_format"] = get_vector_format_name(settings.translation_format); writer["scale_format"] = get_vector_format_name(settings.scale_format); writer["has_scale"] = clip.has_scale; writer["error_metric"] = settings.error_metric->get_name(); if (are_all_enum_flags_set(stats.logging, stat_logging::detailed) || are_all_enum_flags_set(stats.logging, stat_logging::exhaustive)) { uint32_t num_default_rotation_tracks = 0; uint32_t num_default_translation_tracks = 0; uint32_t num_default_scale_tracks = 0; uint32_t num_constant_rotation_tracks = 0; uint32_t num_constant_translation_tracks = 0; uint32_t num_constant_scale_tracks = 0; uint32_t num_animated_rotation_tracks = 0; uint32_t num_animated_translation_tracks = 0; uint32_t num_animated_scale_tracks = 0; for (const BoneStreams& bone_stream : clip.segments[0].bone_iterator()) { if (bone_stream.is_stripped_from_output()) continue; if (bone_stream.is_rotation_default) num_default_rotation_tracks++; else if (bone_stream.is_rotation_constant) num_constant_rotation_tracks++; else num_animated_rotation_tracks++; if (bone_stream.is_translation_default) num_default_translation_tracks++; else if (bone_stream.is_translation_constant) num_constant_translation_tracks++; else num_animated_translation_tracks++; if (bone_stream.is_scale_default) num_default_scale_tracks++; else if (bone_stream.is_scale_constant) num_constant_scale_tracks++; else num_animated_scale_tracks++; } const uint32_t num_default_tracks = num_default_rotation_tracks + num_default_translation_tracks + num_default_scale_tracks; const uint32_t num_constant_tracks = num_constant_rotation_tracks + num_constant_translation_tracks + num_constant_scale_tracks; const uint32_t num_animated_tracks = num_animated_rotation_tracks + num_animated_translation_tracks + num_animated_scale_tracks; writer["num_default_rotation_tracks"] = num_default_rotation_tracks; writer["num_default_translation_tracks"] = num_default_translation_tracks; writer["num_default_scale_tracks"] = num_default_scale_tracks; writer["num_constant_rotation_tracks"] = num_constant_rotation_tracks; writer["num_constant_translation_tracks"] = num_constant_translation_tracks; writer["num_constant_scale_tracks"] = num_constant_scale_tracks; writer["num_animated_rotation_tracks"] = num_animated_rotation_tracks; writer["num_animated_translation_tracks"] = num_animated_translation_tracks; writer["num_animated_scale_tracks"] = num_animated_scale_tracks; writer["num_default_tracks"] = num_default_tracks; writer["num_constant_tracks"] = num_constant_tracks; writer["num_animated_tracks"] = num_animated_tracks; const uint32_t clip_header_size = sizeof(raw_buffer_header) + sizeof(tracks_header) + sizeof(transform_tracks_header); const uint32_t clip_metadata_common_size = calculate_clip_metadata_common_size(clip, compressed_clip); const uint32_t clip_metadata_rotation_constant_size = get_packed_rotation_size(get_highest_variant_precision(get_rotation_variant(settings.rotation_format))) * num_constant_rotation_tracks; const uint32_t clip_metadata_translation_constant_size = get_packed_vector_size(vector_format8::vector3f_full) * num_constant_translation_tracks; const uint32_t clip_metadata_scale_constant_size = get_packed_vector_size(vector_format8::vector3f_full) * num_constant_scale_tracks; writer["clip_header_size"] = clip_header_size; writer["clip_metadata_common_size"] = clip_metadata_common_size; writer["clip_metadata_rotation_constant_size"] = clip_metadata_rotation_constant_size; writer["clip_metadata_translation_constant_size"] = clip_metadata_translation_constant_size; writer["clip_metadata_scale_constant_size"] = clip_metadata_scale_constant_size; const uint32_t range_rotation_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::rotations) ? get_range_reduction_rotation_size(settings.rotation_format) : 0; const uint32_t range_translation_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::translations) ? k_clip_range_reduction_vector3_range_size : 0; const uint32_t range_scale_size = are_any_enum_flags_set(range_reduction, range_reduction_flags8::scales) ? k_clip_range_reduction_vector3_range_size : 0; const uint32_t clip_metadata_rotation_animated_size = range_rotation_size * num_animated_rotation_tracks; const uint32_t clip_metadata_translation_animated_size = range_translation_size * num_animated_translation_tracks; const uint32_t clip_metadata_scale_animated_size = range_scale_size * num_animated_scale_tracks; writer["clip_metadata_rotation_animated_size"] = clip_metadata_rotation_animated_size; writer["clip_metadata_translation_animated_size"] = clip_metadata_translation_animated_size; writer["clip_metadata_scale_animated_size"] = clip_metadata_scale_animated_size; const uint32_t segment_metadata_common_size = calculate_segment_metadata_common_size(clip, settings); const uint32_t segment_metadata_rotation_size = calculate_segment_metadata_rotation_size(clip, range_reduction); const uint32_t segment_metadata_translation_size = calculate_segment_metadata_translation_size(clip, range_reduction); const uint32_t segment_metadata_scale_size = calculate_segment_metadata_scale_size(clip, range_reduction); const uint32_t segment_animated_rotation_size = calculate_segment_animated_rotation_size(clip); const uint32_t segment_animated_translation_size = calculate_segment_animated_translation_size(clip); const uint32_t segment_animated_scale_size = calculate_segment_animated_scale_size(clip); writer["segment_metadata_common_size"] = segment_metadata_common_size; writer["segment_metadata_rotation_size"] = segment_metadata_rotation_size; writer["segment_metadata_translation_size"] = segment_metadata_translation_size; writer["segment_metadata_scale_size"] = segment_metadata_scale_size; writer["segment_animated_rotation_size"] = segment_animated_rotation_size; writer["segment_animated_translation_size"] = segment_animated_translation_size; writer["segment_animated_scale_size"] = segment_animated_scale_size; uint32_t known_data_size = 0; known_data_size += clip_header_size; known_data_size += clip_metadata_common_size; known_data_size += clip_metadata_rotation_constant_size; known_data_size += clip_metadata_translation_constant_size; known_data_size += clip_metadata_scale_constant_size; known_data_size += clip_metadata_rotation_animated_size; known_data_size += clip_metadata_translation_animated_size; known_data_size += clip_metadata_scale_animated_size; known_data_size += segment_metadata_common_size; known_data_size += segment_metadata_rotation_size; known_data_size += segment_metadata_translation_size; known_data_size += segment_metadata_scale_size; const uint32_t segment_animated_data_size = calculate_segment_animated_data_size(clip); known_data_size += segment_animated_data_size; const int32_t unknown_overhead_size = compressed_size - known_data_size; ACL_ASSERT(unknown_overhead_size >= 0, "Overhead size should be positive"); writer["unknown_overhead_size"] = unknown_overhead_size; } writer["segmenting"] = [&](sjson::ObjectWriter& segmenting_writer) { segmenting_writer["num_segments"] = clip.num_segments; segmenting_writer["ideal_num_samples"] = settings.segmenting.ideal_num_samples; segmenting_writer["max_num_samples"] = settings.segmenting.max_num_samples; }; writer["segments"] = [&](sjson::ArrayWriter& segments_writer) { for (const SegmentContext& segment : clip.segment_iterator()) { segments_writer.push([&](sjson::ObjectWriter& segment_writer) { write_summary_segment_stats(segment, settings.rotation_format, settings.translation_format, settings.scale_format, segment_writer); if (are_all_enum_flags_set(stats.logging, stat_logging::detailed)) write_detailed_segment_stats(segment, segment_writer); if (are_all_enum_flags_set(stats.logging, stat_logging::exhaustive)) write_exhaustive_segment_stats(allocator, segment, raw_clip, additive_base_clip_context, settings, track_list, segment_writer); }); } }; } } } ACL_IMPL_FILE_PRAGMA_POP #endif // #if defined(SJSON_CPP_WRITER)