blob: 078f15b9fcc5aaa4fa8bbf8a4714aec4b9cc13c0 [file] [log] [blame]
// 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.
#include "src/graphics/display/drivers/coordinator/waiting-image-list.h"
#include <lib/driver/logging/cpp/logger.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <cstddef>
#include <utility>
#include <vector>
#include <fbl/ref_ptr.h>
#include "src/graphics/display/drivers/coordinator/fence.h"
#include "src/graphics/display/drivers/coordinator/image.h"
namespace display_coordinator {
WaitingImageList::Entry::Entry(fbl::RefPtr<Image> image, fbl::RefPtr<Fence> wait_fence)
: image_(std::move(image)), wait_fence_(std::move(wait_fence)) {
ZX_DEBUG_ASSERT(image_ != nullptr);
}
fbl::RefPtr<Image> WaitingImageList::Entry::TakeImage() {
ZX_DEBUG_ASSERT(image_ != nullptr);
ZX_DEBUG_ASSERT(wait_fence_ == nullptr);
return std::move(image_);
}
void WaitingImageList::Entry::ResetWaitFence() { wait_fence_ = nullptr; }
void WaitingImageList::Entry::MarkFenceReady(Fence& fence) {
if (wait_fence_.get() != &fence) {
return;
}
wait_fence_ = nullptr;
}
WaitingImageList::~WaitingImageList() {
// Ensure that all waiting fences are reset.
RemoveAllImages();
}
void WaitingImageList::RemoveOldestImages(size_t count) {
ZX_DEBUG_ASSERT(count <= size_);
while (count--) {
RemoveOldestImage();
}
CheckRepresentation();
}
void WaitingImageList::RemoveImage(const Image& image_to_retire) {
// The read/write indices are "logical indices"; they haven't been mapped into the ring buffer.
size_t write_index = 0;
size_t erase_count = 0;
for (size_t read_index = 0; read_index < size_; ++read_index) {
Entry& read_entry = GetEntry(read_index);
const bool should_erase = &image_to_retire == read_entry.image().get();
if (should_erase) {
// Images match, so reset the read entry without moving it.
read_entry = Entry();
++erase_count;
} else {
// Images don't match, so move the read entry into the write entry.
if (read_index != write_index) {
Entry& write_entry = GetEntry(write_index);
write_entry = std::move(read_entry);
}
++write_index;
}
}
size_ -= erase_count;
CheckRepresentation();
}
zx::result<> WaitingImageList::PushImage(fbl::RefPtr<Image> image, fbl::RefPtr<Fence> wait_fence) {
if (size_ >= kMaxSize) {
fdf::error("Failed to allocate waiting-image");
return zx::error_result(ZX_ERR_BAD_STATE);
}
if (wait_fence) {
zx::result status = wait_fence->Wait();
if (status.is_error()) {
fdf::error("Failed to start waiting for image: {}", status);
// Mark the image as ready. Displaying garbage is better than hanging or crashing.
wait_fence = nullptr;
}
}
Entry& new_entry = GetEntry(size_++);
ZX_DEBUG_ASSERT(new_entry.image() == nullptr);
ZX_DEBUG_ASSERT(new_entry.wait_fence() == nullptr);
new_entry = Entry(std::move(image), std::move(wait_fence));
CheckRepresentation();
return zx::ok();
}
fbl::RefPtr<Image> WaitingImageList::PopNewestReadyImage() {
// Count down from most recent image until we find one that is ready.
size_t count = size();
while (count--) {
// Candidate entry, to check if image is ready.
Entry& entry = GetEntry(count);
if (entry.IsReady()) {
// Erase all earlier entries.
RemoveOldestImages(count);
// Must also erase the entry containing the image we found.
// The image is reset by the std::move(). No need to reset the wait fence; it is nullptr.
ZX_DEBUG_ASSERT(size_ > 0);
ZX_DEBUG_ASSERT(&GetEntry(0) == &entry);
ZX_DEBUG_ASSERT(!entry.wait_fence());
--size_;
ring_base_ = (ring_base_ + 1) % kMaxSize;
fbl::RefPtr<Image> image = entry.TakeImage();
CheckRepresentation();
return image;
}
}
// No ready image was found.
CheckRepresentation();
return nullptr;
}
bool WaitingImageList::MarkFenceReady(Fence& fence) {
bool any_image_ready = false;
for (size_t i = 0; i < size(); ++i) {
Entry& entry = GetEntry(i);
entry.MarkFenceReady(fence);
any_image_ready |= entry.IsReady();
}
CheckRepresentation();
return any_image_ready;
}
void WaitingImageList::UpdateLatestClientConfigStamp(display::ConfigStamp stamp) {
if (size_ == 0) {
return;
}
const Entry& entry = GetEntry(size_ - 1);
entry.image()->set_latest_client_config_stamp(stamp);
}
fbl::RefPtr<Image> WaitingImageList::GetNewestImageForTesting() const {
if (size_) {
return GetEntry(size_ - 1).image();
}
return nullptr;
}
std::vector<WaitingImageList::Entry> WaitingImageList::GetFullContentsForTesting() const {
std::vector<WaitingImageList::Entry> result;
result.reserve(size_);
for (size_t i = 0; i < size_; ++i) {
const Entry& entry = GetEntry(i);
result.emplace_back(entry.image(), entry.wait_fence());
}
return result;
}
void WaitingImageList::RemoveOldestImage() {
ZX_DEBUG_ASSERT(size_ > 0);
Entry& entry = GetEntry(0);
ZX_DEBUG_ASSERT(entry.image());
entry.ResetWaitFence();
entry = Entry();
--size_;
ring_base_ = (ring_base_ + 1) % kMaxSize;
}
void WaitingImageList::CheckRepresentation() const {
#ifndef NDEBUG
ZX_ASSERT_MSG(ring_base_ < kMaxSize,
"Invalidity detected: `ring_base_` is out of range (ring_base_=%lu, kMaxSize=%lu)",
ring_base_, kMaxSize);
ZX_ASSERT_MSG(size_ <= kMaxSize,
"Invalidity detected: exceeded maximum size (size_=%lu, kMaxSize=%lu)", size_,
kMaxSize);
// Check validity of active entries.
for (size_t i = 0; i < size_; ++i) {
const Entry& entry = GetEntry(i);
ZX_ASSERT_MSG(entry.image() != nullptr,
"Invalidity detected: image cannot be null. ring_base_: %lu size_: %lu i: %lu",
ring_base_, size_, i);
}
// Check validity of inactive entries.
for (size_t i = size_; i < kMaxSize; ++i) {
// Can't use `GetEntry()` because it would fail bounds check.
const Entry& entry = entries_[(ring_base_ + i) % kMaxSize];
ZX_ASSERT_MSG(entry.image() == nullptr,
"Invalidity detected: image must be null. ring_base_: %lu size_: %lu i: %lu",
ring_base_, size_, i);
ZX_ASSERT_MSG(
entry.wait_fence() == nullptr,
"Invalidity detected: wait fence must be null. ring_base_: %lu size_: %lu i: %lu",
ring_base_, size_, i);
}
#endif // #ifndef NDEBUG
}
} // namespace display_coordinator