blob: 4cb82a5f757b76da572835abc3be6fc46e0fbabd [file] [log] [blame] [edit]
// Copyright 2025 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.
#ifndef SRC_GRAPHICS_DISPLAY_DRIVERS_COORDINATOR_CLIENT_VSYNC_QUEUE_H_
#define SRC_GRAPHICS_DISPLAY_DRIVERS_COORDINATOR_CLIENT_VSYNC_QUEUE_H_
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <cstdint>
#include <type_traits>
#include <fbl/ring_buffer.h>
#include "src/graphics/display/lib/api-types/cpp/config-stamp.h"
#include "src/graphics/display/lib/api-types/cpp/display-id.h"
#include "src/graphics/display/lib/api-types/cpp/vsync-ack-cookie.h"
namespace display_coordinator {
// Traffic shaper for VSync messages sent to a Coordinator client.
//
// Under normal operation, VSync messages are generated at a high frequency.
// Clients designed for throughput rather than latency may occasionally take a
// long time to process a VSync. Without traffic shaping, this would result in a
// long queue of unprocessed FIDL messages, creating memory pressure.
//
// This traffic shaper avoids the situation described above. Clients are
// occasionally asked to acknowledge VSync messages, to provide a backpressure
// signal. When a client falls behind, a fixed number of VSync messages are
// buffered. If the buffer fills up, older messages are discarded in favor of
// newer messages.
//
// Instances are not thread-safe. Concurrent access must be synchronized
// externally.
class ClientVsyncQueue {
public:
// The information in one VSync message.
struct Message {
display::DisplayId display_id;
zx::time_monotonic timestamp;
display::ConfigStamp config_stamp;
friend constexpr bool operator==(const Message&, const Message&) = default;
};
// Throttling starts when the unacknowledged message count exceeds this.
static constexpr int32_t kThrottleWatermark = 120;
// Ack cookie requested when the unacknowledged message count exceeds this.
static constexpr int32_t kRequestAckWatermark = kThrottleWatermark / 2;
// Throttled messages are buffered when
static constexpr int32_t kThrottleBufferSize = 10;
// Creates a queue that does not deliver received VSync events.
ClientVsyncQueue();
// Provided to make tests deterministic.
explicit ClientVsyncQueue(uint64_t cookie_salt);
ClientVsyncQueue(const ClientVsyncQueue&) = delete;
ClientVsyncQueue& operator=(const ClientVsyncQueue&) = delete;
~ClientVsyncQueue();
// Registers a VSync message to be sent to the Coordinator client.
//
// If the queue is full, the oldest VSync message will be overwritten.
//
// `DrainUntilThrottled()` must be called before any other method is called.
void Push(const Message& message);
// Called when a client acknowledges a VSync.
//
// `ack_cookie` must not be invalid.
//
// Returns false if `ack_cookie` is not a correct acknowledgement cookie.
//
// If this method succeeds, `DrainUntilThrottled()` must be called before any
// other method is called.
bool Acknowledge(display::VsyncAckCookie ack_cookie);
// Removes all the messages eligible to be sent to the Coordinator client.
//
// `send_message` must take two arguments, a `const
// ClientVsyncQueue::Message&` and a `display::VsyncAckCookie`, and must
// attempt to send the VSync message described by the arguments.
//
// `send_message` must not make any method calls on this queue.
//
// `send_message` receives messages in the same order that they were given to
// `Push()`.
template <typename Callable>
void DrainUntilThrottled(Callable send_message);
private:
// False if `Acknowledge()` cannot succeed.
bool IsWaitingForAcknowledgement() const {
return unacknowledged_cookie_ != display::kInvalidVsyncAckCookie;
}
// False if the next `Push()` will let its message through.
bool IsThrottling() const {
static_assert(
kThrottleWatermark > kRequestAckWatermark,
"Current implementation assumes the throttle watermark is above the request watermark");
return messages_sent_since_last_acknowledgement_ == kThrottleWatermark;
}
// Returns a new valid `VSyncAckCookie`.
//
// This method is not idempotent. The generated value is expected to be used.
[[nodiscard]] display::VsyncAckCookie GenerateCookie();
// Stores throttled VSync messages.
fbl::RingBuffer<Message, kThrottleBufferSize> queued_messages_;
const uint64_t cookie_salt_ = 0;
uint64_t cookie_sequence_ = 0;
// Unthrottled messages since the last successful `Acknowledge()`.
int32_t messages_sent_since_last_acknowledgement_ = 0;
// Invalid if we don't have a cookie waiting to be acknowledged.
display::VsyncAckCookie unacknowledged_cookie_ = display::kInvalidVsyncAckCookie;
};
template <typename Callable>
void ClientVsyncQueue::DrainUntilThrottled(Callable send_message) {
static_assert(
std::is_invocable_v<Callable, const Message&, display::VsyncAckCookie>,
"Prototype: void send_message(const Message& message, display::VsyncAckCookie ack_cookie)");
while (!queued_messages_.empty()) {
if (IsThrottling()) [[unlikely]] {
break;
}
display::VsyncAckCookie cookie = display::kInvalidVsyncAckCookie;
if (messages_sent_since_last_acknowledgement_ == kRequestAckWatermark) [[unlikely]] {
ZX_DEBUG_ASSERT(unacknowledged_cookie_ == display::kInvalidVsyncAckCookie);
cookie = GenerateCookie();
// This state change technically takes effect after the `send_message()`
// call below completes. We can get away with making the state change now
// because `send_message()` is not allowed to make method calls on this
// instance.
unacknowledged_cookie_ = cookie;
}
const Message& message = queued_messages_.front();
send_message(message, cookie);
queued_messages_.pop();
++messages_sent_since_last_acknowledgement_;
}
ZX_DEBUG_ASSERT(queued_messages_.empty() || IsThrottling());
}
} // namespace display_coordinator
#endif // SRC_GRAPHICS_DISPLAY_DRIVERS_COORDINATOR_CLIENT_VSYNC_QUEUE_H_