blob: a1e4e41281159e1733b5c36840705f83f644b3fc [file] [log] [blame] [edit]
// Copyright 2016 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/lib/framebuffer-display/framebuffer-display.h"
#include <fidl/fuchsia.hardware.pci/cpp/wire.h>
#include <fidl/fuchsia.images2/cpp/wire.h>
#include <fidl/fuchsia.sysmem/cpp/wire.h>
#include <lib/device-protocol/pci.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/image-format/image_format.h>
#include <lib/sysmem-version/sysmem-version.h>
#include <lib/zbi-format/graphics.h>
#include <lib/zx/result.h>
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <cinttypes>
#include <cstdint>
#include <memory>
#include <mutex>
#include <utility>
#include <bind/fuchsia/sysmem/heap/cpp/bind.h>
#include <fbl/alloc_checker.h>
#include "src/graphics/display/lib/api-protocols/cpp/display-engine-events-interface.h"
#include "src/graphics/display/lib/api-types/cpp/alpha-mode.h"
#include "src/graphics/display/lib/api-types/cpp/config-check-result.h"
#include "src/graphics/display/lib/api-types/cpp/coordinate-transformation.h"
#include "src/graphics/display/lib/api-types/cpp/display-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-buffer-collection-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-config-stamp.h"
#include "src/graphics/display/lib/api-types/cpp/driver-image-id.h"
#include "src/graphics/display/lib/api-types/cpp/driver-layer.h"
#include "src/graphics/display/lib/api-types/cpp/image-buffer-usage.h"
#include "src/graphics/display/lib/api-types/cpp/image-metadata.h"
#include "src/graphics/display/lib/api-types/cpp/image-tiling-type.h"
#include "src/graphics/display/lib/api-types/cpp/mode-and-id.h"
#include "src/graphics/display/lib/api-types/cpp/mode-id.h"
#include "src/graphics/display/lib/api-types/cpp/mode.h"
#include "src/graphics/display/lib/api-types/cpp/pixel-format.h"
#include "src/graphics/display/lib/api-types/cpp/power-mode.h"
#include "src/graphics/display/lib/api-types/cpp/rectangle.h"
namespace framebuffer_display {
namespace {
constexpr display::EngineInfo kEngineInfo({
.max_layer_count = 1,
.max_connected_display_count = 1,
.is_capture_supported = false,
});
constexpr display::DisplayId kDisplayId(1);
constexpr display::ModeId kDisplayModeId(1);
constexpr int kRefreshRateHz = 30;
constexpr uint64_t kImageHandle = 0xdecafc0ffee;
constexpr auto kVSyncInterval = zx::usec(1000000 / kRefreshRateHz);
fuchsia_hardware_sysmem::wire::HeapProperties GetHeapProperties(fidl::AnyArena& arena) {
fuchsia_hardware_sysmem::wire::CoherencyDomainSupport coherency_domain_support =
fuchsia_hardware_sysmem::wire::CoherencyDomainSupport::Builder(arena)
.cpu_supported(false)
.ram_supported(true)
.inaccessible_supported(false)
.Build();
fuchsia_hardware_sysmem::wire::HeapProperties heap_properties =
fuchsia_hardware_sysmem::wire::HeapProperties::Builder(arena)
.coherency_domain_support(std::move(coherency_domain_support))
.need_clear(false)
.Build();
return heap_properties;
}
void OnHeapServerClose(fidl::UnbindInfo info, zx::channel channel) {
if (info.is_dispatcher_shutdown()) {
// Pending wait is canceled because the display device that the heap belongs
// to has been destroyed.
fdf::info("Framebuffer display destroyed: status: {}", info.status_string());
return;
}
if (info.is_peer_closed()) {
fdf::info("Client closed heap connection");
return;
}
fdf::error("Channel internal error: status: {}", info.FormatDescription().c_str());
}
zx_koid_t GetCurrentProcessKoid() {
zx_handle_t handle = zx_process_self();
zx_info_handle_basic_t info;
zx_status_t status =
zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
} // namespace
// implement display controller protocol:
display::EngineInfo FramebufferDisplay::CompleteCoordinatorConnection() {
const display::ModeAndId mode_and_id({
.id = kDisplayModeId,
.mode = display::Mode({
.active_width = properties_.width_px,
.active_height = properties_.height_px,
.refresh_rate_millihertz = kRefreshRateHz * 1'000,
}),
});
const cpp20::span<const display::ModeAndId> preferred_modes(&mode_and_id, 1);
const cpp20::span<const display::PixelFormat> pixel_formats(&properties_.pixel_format, 1);
engine_events_.OnDisplayAdded(kDisplayId, preferred_modes, pixel_formats);
return kEngineInfo;
}
zx::result<> FramebufferDisplay::ImportBufferCollection(
display::DriverBufferCollectionId buffer_collection_id,
fidl::ClientEnd<fuchsia_sysmem2::BufferCollectionToken> buffer_collection_token) {
if (buffer_collections_.find(buffer_collection_id) != buffer_collections_.end()) {
fdf::error("Buffer Collection (id={}) already exists", buffer_collection_id.value());
return zx::error(ZX_ERR_ALREADY_EXISTS);
}
ZX_DEBUG_ASSERT_MSG(sysmem_client_.is_valid(), "sysmem allocator is not initialized");
auto [collection_client_endpoint, collection_server_endpoint] =
fidl::Endpoints<fuchsia_sysmem2::BufferCollection>::Create();
fidl::Arena arena;
fuchsia_sysmem2::wire::AllocatorBindSharedCollectionRequest bind_request =
fuchsia_sysmem2::wire::AllocatorBindSharedCollectionRequest::Builder(arena)
.token(std::move(buffer_collection_token))
.buffer_collection_request(std::move(collection_server_endpoint))
.Build();
fidl::OneWayStatus fidl_transport_status =
sysmem_client_->BindSharedCollection(std::move(bind_request));
if (!fidl_transport_status.ok()) {
fdf::error("FIDL error calling BindSharedCollection: {}", fidl_transport_status.error());
return zx::error(fidl_transport_status.status());
}
buffer_collections_[buffer_collection_id] =
fidl::WireSyncClient(std::move(collection_client_endpoint));
return zx::ok();
}
zx::result<> FramebufferDisplay::ReleaseBufferCollection(
display::DriverBufferCollectionId buffer_collection_id) {
if (buffer_collections_.find(buffer_collection_id) == buffer_collections_.end()) {
fdf::error("Cannot release buffer collection {}: buffer collection doesn't exist",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
buffer_collections_.erase(buffer_collection_id);
return zx::ok();
}
zx::result<display::DriverImageId> FramebufferDisplay::ImportImage(
const display::ImageMetadata& image_metadata,
display::DriverBufferCollectionId buffer_collection_id, uint32_t buffer_index) {
const auto it = buffer_collections_.find(buffer_collection_id);
if (it == buffer_collections_.end()) {
fdf::error("ImportImage: Cannot find imported buffer collection (id={})",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
const fidl::WireSyncClient<fuchsia_sysmem2::BufferCollection>& collection = it->second;
fidl::WireResult<fuchsia_sysmem2::BufferCollection::CheckAllBuffersAllocated>
check_buffers_transport_result = collection->CheckAllBuffersAllocated();
if (!check_buffers_transport_result.ok()) {
fdf::error("FIDL error calling CheckAllBuffersAllocated: {}",
check_buffers_transport_result.error());
return zx::error(check_buffers_transport_result.status());
}
fit::result<fuchsia_sysmem2::wire::Error>& check_buffers_domain_result =
check_buffers_transport_result.value();
if (check_buffers_domain_result.is_error()) {
fuchsia_sysmem2::wire::Error& check_buffers_domain_error =
check_buffers_domain_result.error_value();
fdf::warn("CheckAllBuffersAllocated failed: {}",
static_cast<uint32_t>(check_buffers_domain_error));
if (check_buffers_domain_error == fuchsia_sysmem2::Error::kPending) {
return zx::error(ZX_ERR_SHOULD_WAIT);
}
return zx::error(sysmem::V1CopyFromV2Error(check_buffers_domain_error));
}
fidl::WireResult<fuchsia_sysmem2::BufferCollection::WaitForAllBuffersAllocated>
wait_for_buffers_transport_result = collection->WaitForAllBuffersAllocated();
if (!wait_for_buffers_transport_result.ok()) {
fdf::error("FIDL error calling WaitForAllBuffersAllocated: {}",
wait_for_buffers_transport_result.error());
return zx::error(wait_for_buffers_transport_result.status());
}
fit::result<fuchsia_sysmem2::wire::Error,
fuchsia_sysmem2::wire::BufferCollectionWaitForAllBuffersAllocatedResponse*>&
wait_for_buffers_domain_result = wait_for_buffers_transport_result.value();
if (wait_for_buffers_domain_result.is_error()) {
fdf::warn("WaitForAllBuffersAllocated failed: {}",
static_cast<uint32_t>(wait_for_buffers_domain_result.error_value()));
return zx::error(sysmem::V1CopyFromV2Error(wait_for_buffers_domain_result.error_value()));
}
fuchsia_sysmem2::wire::BufferCollectionInfo& collection_info =
wait_for_buffers_domain_result.value()->buffer_collection_info();
if (!collection_info.settings().has_image_format_constraints()) {
fdf::error("no image format constraints");
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (buffer_index > 0) {
fdf::error("invalid index {}, greater than 0", buffer_index);
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
fuchsia_images2::wire::PixelFormat sysmem2_collection_format =
collection_info.settings().image_format_constraints().pixel_format();
if (sysmem2_collection_format != properties_.pixel_format.ToFidl()) {
fdf::error("Image format from sysmem ({}) doesn't match expected format ({})",
static_cast<uint32_t>(sysmem2_collection_format),
properties_.pixel_format.ValueForLogging());
return zx::error(ZX_ERR_INVALID_ARGS);
}
// We only need the VMO temporarily to get the BufferKey. The BufferCollection client_end in
// buffer_collections_ is not SetWeakOk (and therefore is known to be strong at this point), so
// it's not necessary to keep this VMO for the buffer to remain alive.
zx::vmo vmo = std::move(collection_info.buffers()[0].vmo());
fidl::Arena arena;
fidl::WireResult<fuchsia_sysmem2::Allocator::GetVmoInfo> get_vmo_info_transport_result =
sysmem_client_->GetVmoInfo(fuchsia_sysmem2::wire::AllocatorGetVmoInfoRequest::Builder(arena)
.vmo(std::move(vmo))
.Build());
if (!get_vmo_info_transport_result.ok()) {
fdf::error("FIDL error calling GetVmoInfo: {}", get_vmo_info_transport_result.error());
return zx::error(get_vmo_info_transport_result.status());
}
fit::result<fuchsia_sysmem2::wire::Error, fuchsia_sysmem2::wire::AllocatorGetVmoInfoResponse*>&
get_vmo_info_domain_result = get_vmo_info_transport_result.value();
if (get_vmo_info_domain_result.is_error()) {
fdf::warn("GetVmoInfo failed: {}",
static_cast<uint32_t>(get_vmo_info_domain_result.error_value()));
return zx::error(sysmem::V1CopyFromV2Error(get_vmo_info_domain_result.error_value()));
}
fuchsia_sysmem2::wire::AllocatorGetVmoInfoResponse*& vmo_info =
get_vmo_info_domain_result.value();
BufferKey buffer_key(vmo_info->buffer_collection_id(), vmo_info->buffer_index());
bool key_matched;
{
std::lock_guard lock(framebuffer_key_mtx_);
key_matched = framebuffer_key_.has_value() && (*framebuffer_key_ == buffer_key);
}
if (!key_matched) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (image_metadata.width() != properties_.width_px ||
image_metadata.height() != properties_.height_px) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(kImageHandle);
}
zx::result<display::DriverCaptureImageId> FramebufferDisplay::ImportImageForCapture(
display::DriverBufferCollectionId driver_buffer_collection_id, uint32_t index) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
void FramebufferDisplay::ReleaseImage(display::DriverImageId image_id) {
// noop
}
display::ConfigCheckResult FramebufferDisplay::CheckConfiguration(
display::DisplayId display_id, display::ModeId display_mode_id,
cpp20::span<const display::DriverLayer> layers) {
ZX_DEBUG_ASSERT(display_id == kDisplayId);
// TODO(https://fxbug.dev/412450577): Remove the single-layer assumption.
ZX_DEBUG_ASSERT(layers.size() == 1);
if (display_mode_id != kDisplayModeId) {
return display::ConfigCheckResult::kUnsupportedDisplayModes;
}
const display::DriverLayer& layer = layers[0];
const display::Rectangle display_area({
.x = 0,
.y = 0,
.width = properties_.width_px,
.height = properties_.height_px,
});
if (layer.display_destination() != display_area) {
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.image_source() != layer.display_destination()) {
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.image_metadata().dimensions() != layer.image_source().dimensions()) {
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.alpha_mode() != display::AlphaMode::kDisable) {
return display::ConfigCheckResult::kUnsupportedConfig;
}
if (layer.image_source_transformation() != display::CoordinateTransformation::kIdentity) {
return display::ConfigCheckResult::kUnsupportedConfig;
}
return display::ConfigCheckResult::kOk;
}
void FramebufferDisplay::ApplyConfiguration(display::DisplayId display_id,
display::ModeId display_mode_id,
cpp20::span<const display::DriverLayer> layers,
display::DriverConfigStamp config_stamp) {
ZX_DEBUG_ASSERT(display_id == kDisplayId);
ZX_DEBUG_ASSERT(display_mode_id == kDisplayModeId);
ZX_DEBUG_ASSERT(layers.size() == 1);
has_image_ = true;
{
std::lock_guard lock(mtx_);
config_stamp_ = config_stamp;
}
}
zx::result<> FramebufferDisplay::SetBufferCollectionConstraints(
const display::ImageBufferUsage& image_buffer_usage,
display::DriverBufferCollectionId buffer_collection_id) {
const auto it = buffer_collections_.find(buffer_collection_id);
if (it == buffer_collections_.end()) {
fdf::error("SetBufferCollectionConstraints: Cannot find imported buffer collection (id={})",
buffer_collection_id.value());
return zx::error(ZX_ERR_NOT_FOUND);
}
const fidl::WireSyncClient<fuchsia_sysmem2::BufferCollection>& collection = it->second;
const uint32_t bytes_per_pixel = ImageFormatStrideBytesPerWidthPixel(
PixelFormatAndModifier(properties_.pixel_format.ToFidl(), kFormatModifier));
uint32_t bytes_per_row = properties_.row_stride_px * bytes_per_pixel;
fidl::Arena arena;
auto constraints = fuchsia_sysmem2::wire::BufferCollectionConstraints::Builder(arena);
auto buffer_usage = fuchsia_sysmem2::wire::BufferUsage::Builder(arena);
buffer_usage.display(fuchsia_sysmem2::wire::kDisplayUsageLayer);
constraints.usage(buffer_usage.Build());
auto buffer_constraints = fuchsia_sysmem2::wire::BufferMemoryConstraints::Builder(arena);
buffer_constraints.min_size_bytes(0);
buffer_constraints.max_size_bytes(properties_.height_px * bytes_per_row);
buffer_constraints.physically_contiguous_required(false);
buffer_constraints.secure_required(false);
buffer_constraints.ram_domain_supported(true);
buffer_constraints.cpu_domain_supported(true);
auto heap = fuchsia_sysmem2::wire::Heap::Builder(arena);
heap.heap_type(bind_fuchsia_sysmem_heap::HEAP_TYPE_FRAMEBUFFER);
heap.id(0);
buffer_constraints.permitted_heaps(std::array{heap.Build()});
constraints.buffer_memory_constraints(buffer_constraints.Build());
auto image_constraints = fuchsia_sysmem2::wire::ImageFormatConstraints::Builder(arena);
image_constraints.pixel_format(properties_.pixel_format.ToFidl());
image_constraints.pixel_format_modifier(kFormatModifier);
image_constraints.color_spaces(std::array{fuchsia_images2::ColorSpace::kSrgb});
image_constraints.min_size({.width = static_cast<uint32_t>(properties_.width_px),
.height = static_cast<uint32_t>(properties_.height_px)});
image_constraints.max_size({.width = static_cast<uint32_t>(properties_.width_px),
.height = static_cast<uint32_t>(properties_.height_px)});
image_constraints.min_bytes_per_row(bytes_per_row);
image_constraints.max_bytes_per_row(bytes_per_row);
constraints.image_format_constraints(std::array{image_constraints.Build()});
auto set_request = fuchsia_sysmem2::wire::BufferCollectionSetConstraintsRequest::Builder(arena);
set_request.constraints(constraints.Build());
fidl::OneWayStatus set_constraints_transport_status =
collection->SetConstraints(set_request.Build());
if (!set_constraints_transport_status.ok()) {
fdf::error("FIDL error calling SetConstraints: {}", set_constraints_transport_status.error());
return zx::error(set_constraints_transport_status.status());
}
return zx::ok();
}
zx::result<> FramebufferDisplay::SetDisplayPowerMode(display::DisplayId display_id,
display::PowerMode power_mode) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<> FramebufferDisplay::StartCapture(display::DriverCaptureImageId capture_image_id) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<> FramebufferDisplay::ReleaseCapture(display::DriverCaptureImageId capture_image_id) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
zx::result<> FramebufferDisplay::SetMinimumRgb(uint8_t minimum_rgb) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// implement sysmem heap protocol:
void FramebufferDisplay::AllocateVmo(AllocateVmoRequestView request,
AllocateVmoCompleter::Sync& completer) {
BufferKey buffer_key(request->buffer_collection_id, request->buffer_index);
zx_info_handle_count handle_count;
zx_status_t status = framebuffer_mmio_.get_vmo()->get_info(
ZX_INFO_HANDLE_COUNT, &handle_count, sizeof(handle_count), nullptr, nullptr);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
if (handle_count.handle_count != 1) {
completer.ReplyError(ZX_ERR_NO_RESOURCES);
return;
}
zx::vmo vmo;
status = framebuffer_mmio_.get_vmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo);
if (status != ZX_OK) {
completer.ReplyError(status);
}
bool had_framebuffer_key;
{
std::lock_guard lock(framebuffer_key_mtx_);
had_framebuffer_key = framebuffer_key_.has_value();
if (!had_framebuffer_key) {
framebuffer_key_ = buffer_key;
}
}
if (had_framebuffer_key) {
completer.ReplyError(ZX_ERR_NO_RESOURCES);
return;
}
completer.ReplySuccess(std::move(vmo));
}
void FramebufferDisplay::DeleteVmo(DeleteVmoRequestView request,
DeleteVmoCompleter::Sync& completer) {
{
std::lock_guard lock(framebuffer_key_mtx_);
framebuffer_key_.reset();
}
// Semantics of DeleteVmo are to recycle all resources tied to the sysmem allocation before
// replying, so we close the VMO handle here before replying. Even if it shares an object and
// pages with a VMO handle we're not closing, this helps clarify wrt semantics of DeleteVmo.
request->vmo.reset();
completer.Reply();
}
// implement driver object:
zx::result<> FramebufferDisplay::Initialize() {
auto [heap_client, heap_server] = fidl::Endpoints<fuchsia_hardware_sysmem::Heap>::Create();
fidl::OneWayStatus register_heap_transport_status = sysmem_hardware_client_->RegisterHeap(
static_cast<uint64_t>(fuchsia_sysmem::wire::HeapType::kFramebuffer), std::move(heap_client));
if (!register_heap_transport_status.ok()) {
fdf::error("FIDL error calling RegisterHeap: {}", register_heap_transport_status.error());
return zx::error(register_heap_transport_status.status());
}
// Start heap server.
auto arena = std::make_unique<fidl::Arena<512>>();
fuchsia_hardware_sysmem::wire::HeapProperties heap_properties = GetHeapProperties(*arena.get());
async::PostTask(&dispatcher_, [server_end = std::move(heap_server), arena = std::move(arena),
heap_properties = std::move(heap_properties), this]() mutable {
auto binding = fidl::BindServer(&dispatcher_, std::move(server_end), this,
[](FramebufferDisplay* self, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_hardware_sysmem::Heap> server_end) {
OnHeapServerClose(info, server_end.TakeChannel());
});
fidl::OneWayStatus on_register_transport_status =
fidl::WireSendEvent(binding)->OnRegister(std::move(heap_properties));
if (!on_register_transport_status.ok()) {
fdf::error("FIDL error calling OnRegister: {}", on_register_transport_status.error());
}
});
// Start vsync loop.
vsync_task_.Post(&dispatcher_);
fdf::info("Initialized display, {} x {} (stride={} format={})", properties_.width_px,
properties_.height_px, properties_.row_stride_px,
properties_.pixel_format.ValueForLogging());
return zx::ok();
}
FramebufferDisplay::FramebufferDisplay(
display::DisplayEngineEventsInterface* engine_events,
fidl::WireSyncClient<fuchsia_sysmem2::Allocator> sysmem_client,
fidl::WireSyncClient<fuchsia_hardware_sysmem::Sysmem> sysmem_hardware_client,
fdf::MmioBuffer framebuffer_mmio, const DisplayProperties& properties,
async_dispatcher_t* dispatcher)
: sysmem_hardware_client_(std::move(sysmem_hardware_client)),
sysmem_client_(std::move(sysmem_client)),
dispatcher_(*dispatcher),
has_image_(false),
framebuffer_mmio_(std::move(framebuffer_mmio)),
properties_(properties),
next_vsync_time_(zx::clock::get_monotonic()),
engine_events_(*engine_events) {
ZX_DEBUG_ASSERT(dispatcher != nullptr);
ZX_DEBUG_ASSERT(engine_events != nullptr);
if (sysmem_client_) {
zx_koid_t current_process_koid = GetCurrentProcessKoid();
std::string debug_name = "framebuffer-display[" + std::to_string(current_process_koid) + "]";
fidl::Arena arena;
auto set_debug_request =
fuchsia_sysmem2::wire::AllocatorSetDebugClientInfoRequest::Builder(arena);
set_debug_request.name(debug_name);
set_debug_request.id(current_process_koid);
fidl::OneWayStatus set_debug_client_info_transport_status =
sysmem_client_->SetDebugClientInfo(set_debug_request.Build());
if (!set_debug_client_info_transport_status.ok()) {
fdf::error("FIDL error calling SetDebugClientInfo: {}",
set_debug_client_info_transport_status.error());
}
}
}
void FramebufferDisplay::OnPeriodicVSync(async_dispatcher_t* dispatcher, async::TaskBase* task,
zx_status_t status) {
if (status != ZX_OK) {
if (status == ZX_ERR_CANCELED) {
fdf::info("Vsync task is canceled.");
} else {
fdf::error("Failed to run Vsync task: {}", zx::make_result(status));
}
return;
}
display::DriverConfigStamp vsync_config_stamp;
{
std::lock_guard lock(mtx_);
vsync_config_stamp = config_stamp_;
}
if (vsync_config_stamp != display::kInvalidDriverConfigStamp) {
engine_events_.OnDisplayVsync(kDisplayId, next_vsync_time_, vsync_config_stamp);
}
next_vsync_time_ += kVSyncInterval;
zx_status_t post_status = vsync_task_.PostForTime(&dispatcher_, next_vsync_time_);
if (post_status != ZX_OK) {
fdf::error("Failed to post Vsync task for the next Vsync: {}", zx::make_result(status));
}
}
} // namespace framebuffer_display