Repository: nfrechette/acl (cpp)

Issue:
Use switch instead of loop for sample decompression
Vs2015 often fails to unroll the loops, use switch without breaks up to 4

No need to support 1/3, only 2/4.


Discussion:

Patch:
diff --git a/cmake/Toolchain-Android.cmake b/cmake/Toolchain-Android.cmake
--- a/cmake/Toolchain-Android.cmake
+++ b/cmake/Toolchain-Android.cmake
@@ -4,3 +4,6 @@ set(CMAKE_SYSTEM_NAME Android)
 
 # Use the clang tool set because it's support for C++11 is superior
 set(CMAKE_GENERATOR_TOOLSET DefaultClang)
+
+# Make sure we use all our processors when building
+set(CMAKE_ANDROID_PROCESS_MAX $ENV{NUMBER_OF_PROCESSORS})
diff --git a/includes/acl/algorithm/uniformly_sampled/decoder.h b/includes/acl/algorithm/uniformly_sampled/decoder.h
--- a/includes/acl/algorithm/uniformly_sampled/decoder.h
+++ b/includes/acl/algorithm/uniformly_sampled/decoder.h
@@ -115,33 +115,54 @@ namespace acl
 			{
 				static constexpr size_t k_num_samples_to_interpolate = 2;
 
-				inline static Quat_32 interpolate_rotation(const Quat_32 rotations[k_num_samples_to_interpolate], float interpolation_alpha)
+				inline static Quat_32 ACL_SIMD_CALL interpolate_rotation(Quat_32Arg0 rotation0, Quat_32Arg1 rotation1, float interpolation_alpha)
 				{
-					return quat_lerp(rotations[0], rotations[1], interpolation_alpha);
+					return quat_lerp(rotation0, rotation1, interpolation_alpha);
 				}
 
-				inline static Vector4_32 interpolate_vector4(const Vector4_32 vectors[k_num_samples_to_interpolate], float interpolation_alpha)
+				inline static Quat_32 ACL_SIMD_CALL interpolate_rotation(Quat_32Arg0 rotation0, Quat_32Arg1 rotation1, Quat_32Arg2 rotation2, Quat_32Arg3 rotation3, float interpolation_alpha)
 				{
-					return vector_lerp(vectors[0], vectors[1], interpolation_alpha);
+					(void)rotation1;
+					(void)rotation2;
+					(void)rotation3;
+					(void)interpolation_alpha;
+					return rotation0;	// Not implemented, we use linear interpolation
 				}
 
-				//												//   offsets
-				uint32_t track_index;							//   0 |   0
-				uint32_t constant_track_data_offset;			//   4 |   4
-				uint32_t clip_range_data_offset;				//   8 |   8
+				inline static Vector4_32 ACL_SIMD_CALL interpolate_vector4(Vector4_32Arg0 vector0, Vector4_32Arg1 vector1, float interpolation_alpha)
+				{
+					return vector_lerp(vector0, vector1, interpolation_alpha);
+				}
+
+				inline static Vector4_32 ACL_SIMD_CALL interpolate_vector4(Vector4_32Arg0 vector0, Vector4_32Arg1 vector1, Vector4_32Arg2 vector2, Vector4_32Arg3 vector3, float interpolation_alpha)
+				{
+					(void)vector1;
+					(void)vector2;
+					(void)vector3;
+					(void)interpolation_alpha;
+					return vector0;		// Not implemented, we use linear interpolation
+				}
+
+				//													//   offsets
+				uint32_t track_index;								//   0 |   0
+				uint32_t constant_track_data_offset;				//   4 |   4
+				uint32_t clip_range_data_offset;					//   8 |   8
+
+				uint32_t format_per_track_data_offset;				//  12 |  12
+				uint32_t segment_range_data_offset;					//  16 |  16
 
-				uint32_t format_per_track_data_offset;			//  12 |  12
-				uint32_t segment_range_data_offset;				//  16 |  16
+				uint32_t key_frame_byte_offsets[2];					//  20 |  20	// Fixed quantization
+				uint32_t key_frame_bit_offsets[2];					//  28 |  28	// Variable quantization
 
-				uint32_t key_frame_byte_offsets[2];				//  20 |  20	// Fixed quantization
-				uint32_t key_frame_bit_offsets[2];				//  28 |  28	// Variable quantization
+				uint8_t padding[28];								//  36 |  36
 
-				uint8_t padding[28];							//  36 |  36
+				Vector4_32 vectors[k_num_samples_to_interpolate];	//  64 |  64
+				Vector4_32 padding0[2];								//  96 |  96
 
-				//									Total size:	    64 |  64
+				//										Total size:	   128 | 128
 			};
 
-			static_assert(sizeof(SamplingContext) == 64, "Unexpected size");
+			static_assert(sizeof(SamplingContext) == 128, "Unexpected size");
 
 			// We use adapters to wrap the DecompressionSettings
 			// This allows us to re-use the code for skipping and decompressing Vector3 samples
diff --git a/includes/acl/decompression/decompress_data.h b/includes/acl/decompression/decompress_data.h
--- a/includes/acl/decompression/decompress_data.h
+++ b/includes/acl/decompression/decompress_data.h
@@ -25,6 +25,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 #include "acl/core/compiler_utils.h"
+#include "acl/core/memory_utils.h"
 
 ACL_IMPL_FILE_PRAGMA_PUSH
 
@@ -712,6 +713,8 @@ namespace acl
 	template <class SettingsType, class DecompressionContextType, class SamplingContextType>
 	inline Quat_32 ACL_SIMD_CALL decompress_and_interpolate_rotation(const SettingsType& settings, const ClipHeader& header, const DecompressionContextType& decomp_context, SamplingContextType& sampling_context)
 	{
+		static_assert(SamplingContextType::k_num_samples_to_interpolate == 2 || SamplingContextType::k_num_samples_to_interpolate == 4, "Unsupported number of samples");
+
 		Quat_32 interpolated_rotation;
 
 		const BitSetIndexRef track_index_bit_ref(decomp_context.bitset_desc, sampling_context.track_index);
@@ -757,10 +760,12 @@ namespace acl
 
 				constexpr size_t num_key_frames = SamplingContextType::k_num_samples_to_interpolate;
 
-				Vector4_32 rotations_as_vec[num_key_frames];
+				// This part is fairly complex, we'll loop and write to the stack (sampling context)
+				Vector4_32* rotations_as_vec = &sampling_context.vectors[0];
 
 				// Range ignore flags are used to skip range normalization at the clip and/or segment levels
 				// Each sample has two bits like so: sample 0 clip, sample 0 segment, sample 1 clip, sample 1 segment, etc
+				// By default, we never ignore range reduction
 				uint32_t range_ignore_flags = 0;
 
 				if (rotation_format == RotationFormat8::QuatDropW_Variable && settings.is_rotation_format_supported(RotationFormat8::QuatDropW_Variable))
@@ -844,81 +849,192 @@ namespace acl
 					}
 				}
 
+				// Load our samples to avoid working with the stack now that things can be unrolled.
+				// We unroll because even if we work from the stack, with 2 samples the compiler always
+				// unrolls but it fails to keep the values in registers, working from the stack which
+				// is inefficient.
+				Vector4_32 rotation_as_vec0 = rotations_as_vec[0];
+				Vector4_32 rotation_as_vec1 = rotations_as_vec[1];
+				Vector4_32 rotation_as_vec2;
+				Vector4_32 rotation_as_vec3;
+
+				if (static_condition<num_key_frames == 4>::test())
+				{
+					rotation_as_vec2 = rotations_as_vec[2];
+					rotation_as_vec3 = rotations_as_vec[3];
+				}
+				else
+				{
+					rotation_as_vec2 = rotation_as_vec0;
+					rotation_as_vec3 = rotation_as_vec0;
+				}
+
+				const uint32_t num_rotation_components = decomp_context.num_rotation_components;
+
 				if (are_segment_rotations_normalized)
 				{
+					const uint32_t segment_range_min_offset = sampling_context.segment_range_data_offset;
+					const uint32_t segment_range_extent_offset = sampling_context.segment_range_data_offset + (num_rotation_components * sizeof(uint8_t));
+
 					if (rotation_format == RotationFormat8::QuatDropW_Variable && settings.is_rotation_format_supported(RotationFormat8::QuatDropW_Variable))
 					{
-						uint32_t ignore_bit_mask = 0x00000001u << ((num_key_frames - 1) * 2);
-						for (size_t i = 0; i < num_key_frames; ++i)
+						constexpr uint32_t ignore_mask = 0x00000001u << ((num_key_frames - 1) * 2);
+						if ((range_ignore_flags & (ignore_mask >> 0)) == 0)
+						{
+							const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
+							const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
+
+							rotation_as_vec0 = vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
+						}
+
+						if ((range_ignore_flags & (ignore_mask >> 2)) == 0)
+						{
+							const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
+							const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
+
+							rotation_as_vec1 = vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
+						}
+
+						if (static_condition<num_key_frames == 4>::test())
 						{
-							if ((range_ignore_flags & ignore_bit_mask) == 0)
+							if ((range_ignore_flags & (ignore_mask >> 4)) == 0)
 							{
-								const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset);
-								const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset + (decomp_context.num_rotation_components * sizeof(uint8_t)));
+								const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_min_offset);
+								const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_extent_offset);
 
-								rotations_as_vec[i] = vector_mul_add(rotations_as_vec[i], segment_range_extent, segment_range_min);
+								rotation_as_vec2 = vector_mul_add(rotation_as_vec2, segment_range_extent, segment_range_min);
 							}
 
-							ignore_bit_mask >>= 2;
+							if ((range_ignore_flags & (ignore_mask >> 8)) == 0)
+							{
+								const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_min_offset);
+								const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_extent_offset);
+
+								rotation_as_vec3 = vector_mul_add(rotation_as_vec3, segment_range_extent, segment_range_min);
+							}
 						}
 					}
 					else
 					{
 						if (rotation_format == RotationFormat8::Quat_128 && settings.is_rotation_format_supported(RotationFormat8::Quat_128))
 						{
-							for (size_t i = 0; i < num_key_frames; ++i)
 							{
-								const Vector4_32 segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset, true);
-								const Vector4_32 segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset + (decomp_context.num_rotation_components * sizeof(uint8_t)), true);
+								const Vector4_32 segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[0] + segment_range_min_offset, true);
+								const Vector4_32 segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[0] + segment_range_extent_offset, true);
+
+								rotation_as_vec0 = vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
+							}
+
+							{
+								const Vector4_32 segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[1] + segment_range_min_offset, true);
+								const Vector4_32 segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[1] + segment_range_extent_offset, true);
 
-								rotations_as_vec[i] = vector_mul_add(rotations_as_vec[i], segment_range_extent, segment_range_min);
+								rotation_as_vec1 = vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
+							}
+
+							if (static_condition<num_key_frames == 4>::test())
+							{
+								{
+									const Vector4_32 segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[2] + segment_range_min_offset, true);
+									const Vector4_32 segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[2] + segment_range_extent_offset, true);
+
+									rotation_as_vec2 = vector_mul_add(rotation_as_vec2, segment_range_extent, segment_range_min);
+								}
+
+								{
+									const Vector4_32 segment_range_min = unpack_vector4_32(decomp_context.segment_range_data[3] + segment_range_min_offset, true);
+									const Vector4_32 segment_range_extent = unpack_vector4_32(decomp_context.segment_range_data[3] + segment_range_extent_offset, true);
+
+									rotation_as_vec3 = vector_mul_add(rotation_as_vec3, segment_range_extent, segment_range_min);
+								}
 							}
 						}
 						else
 						{
-							for (size_t i = 0; i < num_key_frames; ++i)
 							{
-								const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset);
-								const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset + (decomp_context.num_rotation_components * sizeof(uint8_t)));
+								const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
+								const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
+
+								rotation_as_vec0 = vector_mul_add(rotation_as_vec0, segment_range_extent, segment_range_min);
+							}
+
+							{
+								const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
+								const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
+
+								rotation_as_vec1 = vector_mul_add(rotation_as_vec1, segment_range_extent, segment_range_min);
+							}
+
+							if (static_condition<num_key_frames == 4>::test())
+							{
+								{
+									const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_min_offset);
+									const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_extent_offset);
+
+									rotation_as_vec2 = vector_mul_add(rotation_as_vec2, segment_range_extent, segment_range_min);
+								}
+
+								{
+									const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_min_offset);
+									const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_extent_offset);
 
-								rotations_as_vec[i] = vector_mul_add(rotations_as_vec[i], segment_range_extent, segment_range_min);
+									rotation_as_vec3 = vector_mul_add(rotation_as_vec3, segment_range_extent, segment_range_min);
+								}
 							}
 						}
 					}
 
-					sampling_context.segment_range_data_offset += decomp_context.num_rotation_components * k_segment_range_reduction_num_bytes_per_component * 2;
+					sampling_context.segment_range_data_offset += num_rotation_components * k_segment_range_reduction_num_bytes_per_component * 2;
 				}
 
 				if (are_clip_rotations_normalized)
 				{
 					const Vector4_32 clip_range_min = vector_unaligned_load_32(decomp_context.clip_range_data + sampling_context.clip_range_data_offset);
-					const Vector4_32 clip_range_extent = vector_unaligned_load_32(decomp_context.clip_range_data + sampling_context.clip_range_data_offset + (decomp_context.num_rotation_components * sizeof(float)));
+					const Vector4_32 clip_range_extent = vector_unaligned_load_32(decomp_context.clip_range_data + sampling_context.clip_range_data_offset + (num_rotation_components * sizeof(float)));
 
-					uint32_t ignore_bit_mask = 0x00000002u << ((num_key_frames - 1) * 2);
-					for (size_t i = 0; i < num_key_frames; ++i)
+					constexpr uint32_t ignore_mask = 0x00000002u << ((num_key_frames - 1) * 2);
+					if ((range_ignore_flags & (ignore_mask >> 0)) == 0)
+						rotation_as_vec0 = vector_mul_add(rotation_as_vec0, clip_range_extent, clip_range_min);
+
+					if ((range_ignore_flags & (ignore_mask >> 2)) == 0)
+						rotation_as_vec1 = vector_mul_add(rotation_as_vec1, clip_range_extent, clip_range_min);
+
+					if (static_condition<num_key_frames == 4>::test())
 					{
-						if ((range_ignore_flags & ignore_bit_mask) == 0)
-							rotations_as_vec[i] = vector_mul_add(rotations_as_vec[i], clip_range_extent, clip_range_min);
+						if ((range_ignore_flags & (ignore_mask >> 4)) == 0)
+							rotation_as_vec2 = vector_mul_add(rotation_as_vec2, clip_range_extent, clip_range_min);
 
-						ignore_bit_mask >>= 2;
+						if ((range_ignore_flags & (ignore_mask >> 8)) == 0)
+							rotation_as_vec3 = vector_mul_add(rotation_as_vec3, clip_range_extent, clip_range_min);
 					}
 
-					sampling_context.clip_range_data_offset += decomp_context.num_rotation_components * sizeof(float) * 2;
+					sampling_context.clip_range_data_offset += num_rotation_components * sizeof(float) * 2;
 				}
 
-				Quat_32 rotations[num_key_frames];
-				if (rotation_format == RotationFormat8::Quat_128 && settings.is_rotation_format_supported(RotationFormat8::Quat_128))
+				// No-op conversion
+				Quat_32 rotation0 = vector_to_quat(rotation_as_vec0);
+				Quat_32 rotation1 = vector_to_quat(rotation_as_vec1);
+				Quat_32 rotation2 = vector_to_quat(rotation_as_vec2);
+				Quat_32 rotation3 = vector_to_quat(rotation_as_vec3);
+
+				if (rotation_format != RotationFormat8::Quat_128 || !settings.is_rotation_format_supported(RotationFormat8::Quat_128))
 				{
-					for (size_t i = 0; i < num_key_frames; ++i)
-						rotations[i] = vector_to_quat(rotations_as_vec[i]);
+					// We dropped the W component
+					rotation0 = quat_from_positive_w(rotation_as_vec0);
+					rotation1 = quat_from_positive_w(rotation_as_vec1);
+
+					if (static_condition<num_key_frames == 4>::test())
+					{
+						rotation2 = quat_from_positive_w(rotation_as_vec2);
+						rotation3 = quat_from_positive_w(rotation_as_vec3);
+					}
 				}
+
+				if (static_condition<num_key_frames == 4>::test())
+					interpolated_rotation = SamplingContextType::interpolate_rotation(rotation0, rotation1, rotation2, rotation3, decomp_context.interpolation_alpha);
 				else
-				{
-					for (size_t i = 0; i < num_key_frames; ++i)
-						rotations[i] = quat_from_positive_w(rotations_as_vec[i]);
-				}
+					interpolated_rotation = SamplingContextType::interpolate_rotation(rotation0, rotation1, decomp_context.interpolation_alpha);
 
-				interpolated_rotation = SamplingContextType::interpolate_rotation(rotations, decomp_context.interpolation_alpha);
 				ACL_ASSERT(quat_is_finite(interpolated_rotation), "Rotation is not valid!");
 				ACL_ASSERT(quat_is_normalized(interpolated_rotation), "Rotation is not normalized!");
 			}
@@ -931,6 +1047,8 @@ namespace acl
 	template<class SettingsAdapterType, class DecompressionContextType, class SamplingContextType>
 	inline Vector4_32 ACL_SIMD_CALL decompress_and_interpolate_vector(const SettingsAdapterType& settings, const ClipHeader& header, const DecompressionContextType& decomp_context, SamplingContextType& sampling_context)
 	{
+		static_assert(SamplingContextType::k_num_samples_to_interpolate == 2 || SamplingContextType::k_num_samples_to_interpolate == 4, "Unsupported number of samples");
+
 		Vector4_32 interpolated_vector;
 
 		const BitSetIndexRef track_index_bit_ref(decomp_context.bitset_desc, sampling_context.track_index);
@@ -958,10 +1076,12 @@ namespace acl
 
 				constexpr size_t num_key_frames = SamplingContextType::k_num_samples_to_interpolate;
 
-				Vector4_32 vectors[num_key_frames];
+				// This part is fairly complex, we'll loop and write to the stack (sampling context)
+				Vector4_32* vectors = &sampling_context.vectors[0];
 
 				// Range ignore flags are used to skip range normalization at the clip and/or segment levels
 				// Each sample has two bits like so: sample 0 clip, sample 0 segment, sample 1 clip, sample 1 segment, etc
+				// By default, we never ignore range reduction
 				uint32_t range_ignore_flags = 0;
 
 				if (format == VectorFormat8::Vector3_Variable && settings.is_vector_format_supported(VectorFormat8::Vector3_Variable))
@@ -1030,21 +1150,66 @@ namespace acl
 					}
 				}
 
+				// Load our samples to avoid working with the stack now that things can be unrolled.
+				// We unroll because even if we work from the stack, with 2 samples the compiler always
+				// unrolls but it fails to keep the values in registers, working from the stack which
+				// is inefficient.
+				Vector4_32 vector0 = vectors[0];
+				Vector4_32 vector1 = vectors[1];
+				Vector4_32 vector2;
+				Vector4_32 vector3;
+
+				if (static_condition<num_key_frames == 4>::test())
+				{
+					vector2 = vectors[2];
+					vector3 = vectors[3];
+				}
+				else
+				{
+					vector2 = vector0;
+					vector3 = vector0;
+				}
+
 				const RangeReductionFlags8 range_reduction_flag = settings.get_range_reduction_flag();
 				if (are_any_enum_flags_set(segment_range_reduction, range_reduction_flag))
 				{
-					uint32_t ignore_bit_mask = 0x00000001u << ((num_key_frames - 1) * 2);
-					for (size_t i = 0; i < num_key_frames; ++i)
+					const uint32_t segment_range_min_offset = sampling_context.segment_range_data_offset;
+					const uint32_t segment_range_extent_offset = sampling_context.segment_range_data_offset + (3 * sizeof(uint8_t));
+
+					constexpr uint32_t ignore_mask = 0x00000001u << ((num_key_frames - 1) * 2);
+					if ((range_ignore_flags & (ignore_mask >> 0)) == 0)
+					{
+						const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_min_offset);
+						const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[0] + segment_range_extent_offset);
+
+						vector0 = vector_mul_add(vector0, segment_range_extent, segment_range_min);
+					}
+
+					if ((range_ignore_flags & (ignore_mask >> 2)) == 0)
 					{
-						if (format != VectorFormat8::Vector3_Variable || !settings.is_vector_format_supported(VectorFormat8::Vector3_Variable) || (range_ignore_flags & ignore_bit_mask) == 0)
+						const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_min_offset);
+						const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[1] + segment_range_extent_offset);
+
+						vector1 = vector_mul_add(vector1, segment_range_extent, segment_range_min);
+					}
+
+					if (static_condition<num_key_frames == 4>::test())
+					{
+						if ((range_ignore_flags & (ignore_mask >> 4)) == 0)
 						{
-							const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset);
-							const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[i] + sampling_context.segment_range_data_offset + (3 * sizeof(uint8_t)));
+							const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_min_offset);
+							const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[2] + segment_range_extent_offset);
 
-							vectors[i] = vector_mul_add(vectors[i], segment_range_extent, segment_range_min);
+							vector2 = vector_mul_add(vector2, segment_range_extent, segment_range_min);
 						}
 
-						ignore_bit_mask >>= 2;
+						if ((range_ignore_flags & (ignore_mask >> 8)) == 0)
+						{
+							const Vector4_32 segment_range_min = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_min_offset);
+							const Vector4_32 segment_range_extent = unpack_vector3_u24_unsafe(decomp_context.segment_range_data[3] + segment_range_extent_offset);
+
+							vector3 = vector_mul_add(vector3, segment_range_extent, segment_range_min);
+						}
 					}
 
 					sampling_context.segment_range_data_offset += 3 * k_segment_range_reduction_num_bytes_per_component * 2;
@@ -1055,19 +1220,30 @@ namespace acl
 					const Vector4_32 clip_range_min = unpack_vector3_96_unsafe(decomp_context.clip_range_data + sampling_context.clip_range_data_offset);
 					const Vector4_32 clip_range_extent = unpack_vector3_96_unsafe(decomp_context.clip_range_data + sampling_context.clip_range_data_offset + (3 * sizeof(float)));
 
-					uint32_t ignore_bit_mask = 0x00000002u << ((num_key_frames - 1) * 2);
-					for (size_t i = 0; i < num_key_frames; ++i)
+					constexpr uint32_t ignore_mask = 0x00000002u << ((num_key_frames - 1) * 2);
+					if ((range_ignore_flags & (ignore_mask >> 0)) == 0)
+						vector0 = vector_mul_add(vector0, clip_range_extent, clip_range_min);
+
+					if ((range_ignore_flags & (ignore_mask >> 2)) == 0)
+						vector1 = vector_mul_add(vector1, clip_range_extent, clip_range_min);
+
+					if (static_condition<num_key_frames == 4>::test())
 					{
-						if ((range_ignore_flags & ignore_bit_mask) == 0)
-							vectors[i] = vector_mul_add(vectors[i], clip_range_extent, clip_range_min);
+						if ((range_ignore_flags & (ignore_mask >> 4)) == 0)
+							vector2 = vector_mul_add(vector2, clip_range_extent, clip_range_min);
 
-						ignore_bit_mask >>= 2;
+						if ((range_ignore_flags & (ignore_mask >> 8)) == 0)
+							vector3 = vector_mul_add(vector3, clip_range_extent, clip_range_min);
 					}
 
 					sampling_context.clip_range_data_offset += k_clip_range_reduction_vector3_range_size;
 				}
 
-				interpolated_vector = SamplingContextType::interpolate_vector4(vectors, decomp_context.interpolation_alpha);
+				if (static_condition<num_key_frames == 4>::test())
+					interpolated_vector = SamplingContextType::interpolate_vector4(vector0, vector1, vector2, vector3, decomp_context.interpolation_alpha);
+				else
+					interpolated_vector = SamplingContextType::interpolate_vector4(vector0, vector1, decomp_context.interpolation_alpha);
+
 				ACL_ASSERT(vector_is_finite3(interpolated_vector), "Vector is not valid!");
 			}
 		}
diff --git a/tools/acl_compressor/main_android/CMakeLists.txt b/tools/acl_compressor/main_android/CMakeLists.txt
--- a/tools/acl_compressor/main_android/CMakeLists.txt
+++ b/tools/acl_compressor/main_android/CMakeLists.txt
@@ -4,8 +4,8 @@ project(acl_compressor)
 set(CMAKE_CXX_STANDARD 11)
 
 set(CMAKE_ANDROID_ARCH armv7-a)
-set(CMAKE_ANDROID_API_MIN 21)
-set(CMAKE_ANDROID_API 21)
+set(CMAKE_ANDROID_API_MIN 24)
+set(CMAKE_ANDROID_API 24)
 set(CMAKE_ANDROID_GUI 1)
 
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
@@ -58,6 +58,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_SKIP_ANT_STEP 0)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_PROGUARD 1)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_PROGUARD_CONFIG_PATH proguard-android.txt)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_SECURE_PROPS_PATH /definitely/insecure)
+set_target_properties(${PROJECT_NAME} PROPERTIES VS_GLOBAL_AndroidBuildSystem "AntBuild")				# Use Ant as our build system
 
 set_property(TARGET ${PROJECT_NAME} PROPERTY ANDROID_NATIVE_LIB_DIRECTORIES $<TARGET_FILE_DIR:${PROJECT_NAME}>)
 set_property(TARGET ${PROJECT_NAME} PROPERTY ANDROID_NATIVE_LIB_DEPENDENCIES ${PROJECT_NAME}.so)
diff --git a/tools/acl_decompressor/main_android/CMakeLists.txt b/tools/acl_decompressor/main_android/CMakeLists.txt
--- a/tools/acl_decompressor/main_android/CMakeLists.txt
+++ b/tools/acl_decompressor/main_android/CMakeLists.txt
@@ -4,8 +4,8 @@ project(acl_decompressor)
 set(CMAKE_CXX_STANDARD 11)
 
 set(CMAKE_ANDROID_ARCH armv7-a)
-set(CMAKE_ANDROID_API_MIN 21)
-set(CMAKE_ANDROID_API 21)
+set(CMAKE_ANDROID_API_MIN 24)
+set(CMAKE_ANDROID_API 24)
 set(CMAKE_ANDROID_GUI 1)
 
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
@@ -78,6 +78,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_SKIP_ANT_STEP 0)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_PROGUARD 1)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_PROGUARD_CONFIG_PATH proguard-android.txt)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_SECURE_PROPS_PATH /definitely/insecure)
+set_target_properties(${PROJECT_NAME} PROPERTIES VS_GLOBAL_AndroidBuildSystem "AntBuild")				# Use Ant as our build system
 
 set_property(TARGET ${PROJECT_NAME} PROPERTY ANDROID_NATIVE_LIB_DIRECTORIES $<TARGET_FILE_DIR:${PROJECT_NAME}>)
 set_property(TARGET ${PROJECT_NAME} PROPERTY ANDROID_NATIVE_LIB_DEPENDENCIES ${PROJECT_NAME}.so)
diff --git a/tools/regression_tester_android/CMakeLists.txt b/tools/regression_tester_android/CMakeLists.txt
--- a/tools/regression_tester_android/CMakeLists.txt
+++ b/tools/regression_tester_android/CMakeLists.txt
@@ -4,8 +4,8 @@ project(acl_regression_tester_android)
 set(CMAKE_CXX_STANDARD 11)
 
 set(CMAKE_ANDROID_ARCH armv7-a)
-set(CMAKE_ANDROID_API_MIN 21)
-set(CMAKE_ANDROID_API 21)
+set(CMAKE_ANDROID_API_MIN 24)
+set(CMAKE_ANDROID_API 24)
 set(CMAKE_ANDROID_GUI 1)
 
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
@@ -70,7 +70,7 @@ if(NOT USE_SIMD_INSTRUCTIONS)
 	add_definitions(-DACL_NO_INTRINSICS)
 endif()
 
-# Disable allocation track since if we fail a regression test, we'll throw an exception
+# Disable allocation tracking since if we fail a regression test, we'll throw an exception
 # and fail to free memory, leading to a crash in the allocator
 add_definitions(-DACL_NO_ALLOCATOR_TRACKING)
 
@@ -83,6 +83,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_SKIP_ANT_STEP 0)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_PROGUARD 1)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_PROGUARD_CONFIG_PATH proguard-android.txt)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_SECURE_PROPS_PATH /definitely/insecure)
+set_target_properties(${PROJECT_NAME} PROPERTIES VS_GLOBAL_AndroidBuildSystem "AntBuild")				# Use Ant as our build system
 
 set_property(TARGET ${PROJECT_NAME} PROPERTY ANDROID_NATIVE_LIB_DIRECTORIES $<TARGET_FILE_DIR:${PROJECT_NAME}>)
 set_property(TARGET ${PROJECT_NAME} PROPERTY ANDROID_NATIVE_LIB_DEPENDENCIES ${PROJECT_NAME}.so)


Test patch:
diff --git a/tests/main_android/CMakeLists.txt b/tests/main_android/CMakeLists.txt
--- a/tests/main_android/CMakeLists.txt
+++ b/tests/main_android/CMakeLists.txt
@@ -4,8 +4,8 @@ project(acl_unit_tests)
 set(CMAKE_CXX_STANDARD 11)
 
 set(CMAKE_ANDROID_ARCH armv7-a)
-set(CMAKE_ANDROID_API_MIN 21)
-set(CMAKE_ANDROID_API 21)
+set(CMAKE_ANDROID_API_MIN 24)
+set(CMAKE_ANDROID_API 24)
 set(CMAKE_ANDROID_GUI 1)
 
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
@@ -60,6 +60,7 @@ set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_SKIP_ANT_STEP 0)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_PROGUARD 1)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_PROGUARD_CONFIG_PATH proguard-android.txt)
 set_target_properties(${PROJECT_NAME} PROPERTIES ANDROID_SECURE_PROPS_PATH /definitely/insecure)
+set_target_properties(${PROJECT_NAME} PROPERTIES VS_GLOBAL_AndroidBuildSystem "AntBuild")				# Use Ant as our build system
 
 set_property(TARGET ${PROJECT_NAME} PROPERTY ANDROID_NATIVE_LIB_DIRECTORIES $<TARGET_FILE_DIR:${PROJECT_NAME}>)
 set_property(TARGET ${PROJECT_NAME} PROPERTY ANDROID_NATIVE_LIB_DEPENDENCIES ${PROJECT_NAME}.so)