blob: 4158c4670dfeeb60b26214fdfe3a495aa5714467 [file]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "timeline_function.h"
#include <lib/syslog/cpp/macros.h>
#include <zircon/assert.h>
#include <limits>
#include <utility>
namespace media {
// static
// Translates a given reference value through a provided timeline function, producing a
// corresponding subject value. Returns kOverflow if result can't fit in an int64_t.
int64_t TimelineFunction::Apply(int64_t subject_time, int64_t reference_time, TimelineRate rate,
int64_t reference_input) {
// Round down (toward negative infinity) when scaling. This preserves scaled distances between
// positive and negative points on the timeline.
// For example, suppose we call this twice:
//
// 1. reference_input - reference_time = 20, ratio = 1/8, scaled_value = 2.5
// 2. reference_input - reference_time = -20, ratio = 1/8, scaled_value = -2.5
//
// If we truncate (round toward zero), the scaled values are 2 and -2, for a difference of 4,
// while the true scaled difference should be 40*1/8 = 5. If we round down, the scaled values are
// 2 and -3, for a (correct) difference of 5.
//
// Perform subtraction in 128-bit space to prevent signed integer overflow undefined behavior when
// handling distant timestamps (e.g. INT64_MAX - (-10)).
__int128_t reference_delta = static_cast<__int128_t>(reference_input) - reference_time;
int64_t scaled_value = rate.Scale128(reference_delta, TimelineRate::RoundingMode::Floor);
if (scaled_value == TimelineRate::kOverflow || scaled_value == TimelineRate::kUnderflow) {
return scaled_value;
}
// Perform addition in 128-bit space and explicitly check bounds before casting down to int64_t.
// Doing 64-bit addition first would trigger C++ UB on overflow, allowing compiler optimizations
// to elide post-addition bounds checks.
__int128_t result_value_128 = static_cast<__int128_t>(scaled_value) + subject_time;
if (result_value_128 > TimelineRate::kOverflow) {
return TimelineRate::kOverflow;
}
if (result_value_128 < TimelineRate::kUnderflow) {
return TimelineRate::kUnderflow;
}
return static_cast<int64_t>(result_value_128);
}
// static
// Combine two given timeline functions, forming a new one. ASSERT upon overflow.
TimelineFunction TimelineFunction::Compose(const TimelineFunction& bc, const TimelineFunction& ab,
bool exact) {
// This composition approach may compromise range and accuracy (in some cases) for simplicity.
// TODO(https://fxbug.dev/42082948): more accuracy here
auto scaled_subject_time = bc.Apply(ab.subject_time());
if (exact) {
ZX_ASSERT(scaled_subject_time != TimelineRate::kOverflow);
ZX_ASSERT(scaled_subject_time != TimelineRate::kUnderflow);
}
return TimelineFunction(scaled_subject_time, ab.reference_time(),
TimelineRate::Product(ab.rate(), bc.rate(), exact));
}
} // namespace media