blob: 259b532aecf385b8714aa5b5d776118fa8aa47c8 [file] [log] [blame]
// Copyright 2020 The Chromium 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 "components/viz/service/display_embedder/output_presenter_fuchsia.h"
#include <fuchsia/sysmem/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/process/process_handle.h"
#include "base/trace_event/trace_event.h"
#include "components/viz/common/features.h"
#include "components/viz/common/gpu/vulkan_context_provider.h"
#include "components/viz/service/display_embedder/skia_output_surface_dependency.h"
#include "gpu/command_buffer/service/external_semaphore_pool.h"
#include "gpu/command_buffer/service/shared_context_state.h"
#include "gpu/ipc/common/gpu_client_ids.h"
#include "gpu/vulkan/vulkan_device_queue.h"
#include "gpu/vulkan/vulkan_function_pointers.h"
#include "gpu/vulkan/vulkan_implementation.h"
#include "third_party/skia/include/gpu/GrBackendSemaphore.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/ozone/public/platform_window_surface.h"
namespace viz {
namespace {
void GrSemaphoresToZxEvents(gpu::VulkanImplementation* vulkan_implementation,
VkDevice vk_device,
const std::vector<GrBackendSemaphore>& semaphores,
std::vector<zx::event>* events) {
for (auto& semaphore : semaphores) {
gpu::SemaphoreHandle handle = vulkan_implementation->GetSemaphoreHandle(
vk_device, semaphore.vkSemaphore());
DCHECK(handle.is_valid());
events->push_back(handle.TakeHandle());
}
}
// Duplicates the given zx::events and stores in gfx::GpuFences.
std::vector<gfx::GpuFence> ZxEventsToGpuFences(
const std::vector<zx::event>& events) {
std::vector<gfx::GpuFence> fences;
for (const auto& event : events) {
gfx::GpuFenceHandle handle;
zx_status_t status =
event.duplicate(ZX_RIGHT_SAME_RIGHTS, &handle.owned_event);
ZX_DCHECK(status == ZX_OK, status);
fences.emplace_back(std::move(handle));
}
return fences;
}
class PresenterImageFuchsia : public OutputPresenter::Image {
public:
explicit PresenterImageFuchsia(uint32_t image_id);
~PresenterImageFuchsia() override;
void BeginPresent() final;
void EndPresent(gfx::GpuFenceHandle release_fence) final;
int GetPresentCount() const final;
void OnContextLost() final;
uint32_t image_id() const { return image_id_; }
void TakeSemaphores(std::vector<GrBackendSemaphore>* read_begin_semaphores,
std::vector<GrBackendSemaphore>* read_end_semaphores);
private:
const uint32_t image_id_;
int present_count_ = 0;
std::unique_ptr<gpu::SharedImageRepresentationSkia::ScopedReadAccess>
read_access_;
std::vector<GrBackendSemaphore> read_begin_semaphores_;
std::vector<GrBackendSemaphore> read_end_semaphores_;
};
PresenterImageFuchsia::PresenterImageFuchsia(uint32_t image_id)
: image_id_(image_id) {}
PresenterImageFuchsia::~PresenterImageFuchsia() {
DCHECK(read_begin_semaphores_.empty());
DCHECK(read_end_semaphores_.empty());
}
void PresenterImageFuchsia::BeginPresent() {
++present_count_;
if (present_count_ == 1) {
DCHECK(!read_access_);
DCHECK(read_begin_semaphores_.empty());
DCHECK(read_end_semaphores_.empty());
read_access_ = skia_representation()->BeginScopedReadAccess(
&read_begin_semaphores_, &read_end_semaphores_);
}
}
void PresenterImageFuchsia::EndPresent(gfx::GpuFenceHandle release_fence) {
DCHECK(present_count_);
DCHECK(release_fence.is_null());
--present_count_;
if (!present_count_)
read_access_.reset();
}
int PresenterImageFuchsia::GetPresentCount() const {
return present_count_;
}
void PresenterImageFuchsia::OnContextLost() {
// Nothing to do here.
}
void PresenterImageFuchsia::TakeSemaphores(
std::vector<GrBackendSemaphore>* read_begin_semaphores,
std::vector<GrBackendSemaphore>* read_end_semaphores) {
DCHECK(read_begin_semaphores->empty());
std::swap(*read_begin_semaphores, read_begin_semaphores_);
DCHECK(read_end_semaphores->empty());
std::swap(*read_end_semaphores, read_end_semaphores_);
}
} // namespace
OutputPresenterFuchsia::PendingOverlay::PendingOverlay(
OverlayCandidate candidate,
std::vector<gfx::GpuFence> release_fences)
: candidate(std::move(candidate)),
release_fences(std::move(release_fences)) {}
OutputPresenterFuchsia::PendingOverlay::~PendingOverlay() = default;
OutputPresenterFuchsia::PendingOverlay::PendingOverlay(PendingOverlay&&) =
default;
OutputPresenterFuchsia::PendingOverlay&
OutputPresenterFuchsia::PendingOverlay::operator=(PendingOverlay&&) = default;
OutputPresenterFuchsia::PendingFrame::PendingFrame(uint32_t ordinal)
: ordinal(ordinal) {}
OutputPresenterFuchsia::PendingFrame::~PendingFrame() = default;
OutputPresenterFuchsia::PendingFrame::PendingFrame(PendingFrame&&) = default;
OutputPresenterFuchsia::PendingFrame&
OutputPresenterFuchsia::PendingFrame::operator=(PendingFrame&&) = default;
// static
std::unique_ptr<OutputPresenterFuchsia> OutputPresenterFuchsia::Create(
ui::PlatformWindowSurface* window_surface,
SkiaOutputSurfaceDependency* deps,
gpu::SharedImageFactory* shared_image_factory,
gpu::SharedImageRepresentationFactory* representation_factory) {
if (!base::FeatureList::IsEnabled(
features::kUseSkiaOutputDeviceBufferQueue)) {
return {};
}
// SetTextureToNewImagePipe() will call ScenicSession::Present() to send
// CreateImagePipe2Cmd creation command, but it will be processed only after
// vsync, which will delay buffer allocation of buffers in AllocateImages(),
// but that shouldn't cause any issues.
fuchsia::images::ImagePipe2Ptr image_pipe;
if (!window_surface->SetTextureToNewImagePipe(image_pipe.NewRequest()))
return {};
return std::make_unique<OutputPresenterFuchsia>(std::move(image_pipe), deps,
shared_image_factory,
representation_factory);
}
OutputPresenterFuchsia::OutputPresenterFuchsia(
fuchsia::images::ImagePipe2Ptr image_pipe,
SkiaOutputSurfaceDependency* deps,
gpu::SharedImageFactory* shared_image_factory,
gpu::SharedImageRepresentationFactory* representation_factory)
: image_pipe_(std::move(image_pipe)),
dependency_(deps),
shared_image_factory_(shared_image_factory),
shared_image_representation_factory_(representation_factory) {
sysmem_allocator_ = base::ComponentContextForProcess()
->svc()
->Connect<fuchsia::sysmem::Allocator>();
sysmem_allocator_->SetDebugClientInfo("CrOutputPresenter",
base::GetCurrentProcId());
image_pipe_.set_error_handler([this](zx_status_t status) {
ZX_LOG(ERROR, status) << "ImagePipe disconnected";
for (auto& frame : pending_frames_) {
std::move(frame.completion_callback)
.Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_FAILED));
}
pending_frames_.clear();
});
}
OutputPresenterFuchsia::~OutputPresenterFuchsia() {}
void OutputPresenterFuchsia::InitializeCapabilities(
OutputSurface::Capabilities* capabilities) {
// We expect origin of buffers is at top left.
capabilities->output_surface_origin = gfx::SurfaceOrigin::kTopLeft;
capabilities->supports_post_sub_buffer = false;
capabilities->supports_commit_overlay_planes = false;
capabilities->supports_surfaceless = true;
capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
kRGBA_8888_SkColorType;
capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
kRGBA_8888_SkColorType;
}
bool OutputPresenterFuchsia::Reshape(const gfx::Size& size,
float device_scale_factor,
const gfx::ColorSpace& color_space,
gfx::BufferFormat format,
gfx::OverlayTransform transform) {
if (!image_pipe_)
return false;
frame_size_ = size;
return true;
}
std::vector<std::unique_ptr<OutputPresenter::Image>>
OutputPresenterFuchsia::AllocateImages(gfx::ColorSpace color_space,
gfx::Size image_size,
size_t num_images) {
if (!image_pipe_)
return {};
// If we already allocated buffer collection then it needs to be released.
if (last_buffer_collection_id_) {
// If there are pending frames for the old buffer collection then remove the
// collection only after that frame is presented. Otherwise remove it now.
if (!pending_frames_.empty() &&
pending_frames_.back().buffer_collection_id ==
last_buffer_collection_id_) {
DCHECK(!pending_frames_.back().remove_buffer_collection);
pending_frames_.back().remove_buffer_collection = true;
} else {
image_pipe_->RemoveBufferCollection(last_buffer_collection_id_);
}
}
buffer_collection_.reset();
// Create buffer collection with 2 extra tokens: one for Vulkan and one for
// the ImagePipe.
fuchsia::sysmem::BufferCollectionTokenSyncPtr collection_token;
sysmem_allocator_->AllocateSharedCollection(collection_token.NewRequest());
collection_token->SetName(100u, "ChromiumPrimaryPlaneOutput");
collection_token->SetDebugClientInfo("vulkan", 0u);
fuchsia::sysmem::BufferCollectionTokenSyncPtr token_for_scenic;
collection_token->Duplicate(ZX_RIGHT_SAME_RIGHTS,
token_for_scenic.NewRequest());
token_for_scenic->SetDebugClientInfo("scenic", 0u);
zx_status_t status = collection_token->Sync();
if (status != ZX_OK) {
ZX_DLOG(ERROR, status) << "fuchsia.sysmem.BufferCollection.Sync()";
return {};
}
auto* vulkan =
dependency_->GetVulkanContextProvider()->GetVulkanImplementation();
// Register the new buffer collection with the ImagePipe.
last_buffer_collection_id_++;
image_pipe_->AddBufferCollection(last_buffer_collection_id_,
std::move(token_for_scenic));
// Register the new buffer collection with Vulkan.
gfx::SysmemBufferCollectionId buffer_collection_id =
gfx::SysmemBufferCollectionId::Create();
VkDevice vk_device = dependency_->GetVulkanContextProvider()
->GetDeviceQueue()
->GetVulkanDevice();
buffer_collection_ = vulkan->RegisterSysmemBufferCollection(
vk_device, buffer_collection_id, collection_token.Unbind().TakeChannel(),
buffer_format_, gfx::BufferUsage::SCANOUT, frame_size_, num_images,
false /* register_with_image_pipe */);
if (!buffer_collection_) {
ZX_DLOG(ERROR, status) << "Failed to allocate sysmem buffer collection";
return {};
}
// Create PresenterImageFuchsia for each buffer in the collection.
uint32_t image_usage =
gpu::SHARED_IMAGE_USAGE_RASTER | gpu::SHARED_IMAGE_USAGE_SCANOUT;
std::vector<std::unique_ptr<OutputPresenter::Image>> images;
images.reserve(num_images);
fuchsia::sysmem::ImageFormat_2 image_format;
image_format.coded_width = frame_size_.width();
image_format.coded_height = frame_size_.height();
// Create an image for each buffer in the collection.
for (size_t i = 0; i < num_images; ++i) {
last_image_id_++;
image_pipe_->AddImage(last_image_id_, last_buffer_collection_id_, i,
image_format);
gfx::GpuMemoryBufferHandle gmb_handle;
gmb_handle.type = gfx::GpuMemoryBufferType::NATIVE_PIXMAP;
gmb_handle.native_pixmap_handle.buffer_collection_id = buffer_collection_id;
gmb_handle.native_pixmap_handle.buffer_index = i;
auto mailbox = gpu::Mailbox::GenerateForSharedImage();
if (!shared_image_factory_->CreateSharedImage(
mailbox, gpu::kDisplayCompositorClientId, std::move(gmb_handle),
buffer_format_, gfx::BufferPlane::DEFAULT, gpu::kNullSurfaceHandle,
frame_size_, color_space, kTopLeft_GrSurfaceOrigin,
kPremul_SkAlphaType, image_usage)) {
return {};
}
auto image = std::make_unique<PresenterImageFuchsia>(last_image_id_);
if (!image->Initialize(shared_image_factory_,
shared_image_representation_factory_, mailbox,
dependency_)) {
return {};
}
images.push_back(std::move(image));
}
return images;
}
void OutputPresenterFuchsia::SwapBuffers(
SwapCompletionCallback completion_callback,
BufferPresentedCallback presentation_callback) {
if (!image_pipe_) {
std::move(completion_callback)
.Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_FAILED));
return;
}
// SwapBuffer() should be called only after SchedulePrimaryPlane().
DCHECK(next_frame_);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
"viz", "OutputPresenterFuchsia::PresentQueue", TRACE_ID_LOCAL(this),
"image_id", next_frame_->image_id);
next_frame_->completion_callback = std::move(completion_callback);
next_frame_->presentation_callback = std::move(presentation_callback);
PresentNextFrame();
}
void OutputPresenterFuchsia::PostSubBuffer(
const gfx::Rect& rect,
SwapCompletionCallback completion_callback,
BufferPresentedCallback presentation_callback) {
// Sub buffer presentation is not supported.
NOTREACHED();
}
void OutputPresenterFuchsia::CommitOverlayPlanes(
SwapCompletionCallback completion_callback,
BufferPresentedCallback presentation_callback) {
// Overlays are not supported yet.
NOTREACHED();
}
void OutputPresenterFuchsia::SchedulePrimaryPlane(
const OverlayProcessorInterface::OutputSurfaceOverlayPlane& plane,
Image* image,
bool is_submitted) {
auto* image_fuchsia = static_cast<PresenterImageFuchsia*>(image);
if (!next_frame_)
next_frame_ = PendingFrame(next_frame_ordinal_++);
DCHECK(!next_frame_->buffer_collection_id);
next_frame_->image_id = image_fuchsia->image_id();
next_frame_->buffer_collection_id = last_buffer_collection_id_;
// Take semaphores for the image and covert them to zx::events that are later
// passed to ImagePipe::PresentImage().
std::vector<GrBackendSemaphore> read_begin_semaphores;
std::vector<GrBackendSemaphore> read_end_semaphores;
image_fuchsia->TakeSemaphores(&read_begin_semaphores, &read_end_semaphores);
DCHECK(!read_begin_semaphores.empty());
DCHECK(!read_end_semaphores.empty());
auto* vulkan_context_provider = dependency_->GetVulkanContextProvider();
auto* vulkan_implementation =
vulkan_context_provider->GetVulkanImplementation();
VkDevice vk_device =
vulkan_context_provider->GetDeviceQueue()->GetVulkanDevice();
GrSemaphoresToZxEvents(vulkan_implementation, vk_device,
read_begin_semaphores, &(next_frame_->acquire_fences));
GrSemaphoresToZxEvents(vulkan_implementation, vk_device, read_end_semaphores,
&(next_frame_->release_fences));
}
void OutputPresenterFuchsia::ScheduleOverlays(
SkiaOutputSurface::OverlayList overlays,
std::vector<ScopedOverlayAccess*> accesses) {
if (!next_frame_)
next_frame_ = PendingFrame(next_frame_ordinal_++);
for (size_t i = 0; i < overlays.size(); ++i) {
auto semaphore = dependency_->GetSharedContextState()
->external_semaphore_pool()
->GetOrCreateSemaphore();
gfx::GpuFenceHandle fence_handle;
fence_handle.owned_event = semaphore.handle().TakeHandle();
accesses[i]->SetReleaseFence(fence_handle.Clone());
std::vector<gfx::GpuFence> release_fences;
release_fences.emplace_back(std::move(fence_handle));
next_frame_->overlays.emplace_back(std::move(overlays[i]),
std::move(release_fences));
// TODO(crbug.com/1144890): Enqueue overlay plane's acquire fences
// after |supports_commit_overlay_planes| is supported. Overlay plane might
// display the same Image more than once, which can create a fence
// dependency that can be broken by a later Image. However, primary plane
// implementation allows only one present at a time. In this scenario,
// merging fences might cause hangs, see crbug.com/1151042.
}
}
void OutputPresenterFuchsia::PresentNextFrame() {
DCHECK(next_frame_);
pending_frames_.push_back(std::move(next_frame_.value()));
next_frame_.reset();
auto& frame = pending_frames_.back();
TRACE_EVENT_NESTABLE_ASYNC_END1("viz", "OutputPresenterFuchsia::PresentQueue",
TRACE_ID_LOCAL(this), "image_id",
frame.image_id);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
"viz", "OutputPresenterFuchsia::PresentFrame", TRACE_ID_LOCAL(this),
"image_id", frame.image_id);
for (size_t i = 0; i < frame.overlays.size(); ++i) {
auto& overlay = frame.overlays[i].candidate;
DCHECK(overlay.mailbox.IsSharedImage());
auto pixmap =
dependency_->GetSharedImageManager()->GetNativePixmap(overlay.mailbox);
if (!pixmap) {
LOG(ERROR) << "Cannot access SysmemNativePixmap";
continue;
}
pixmap->ScheduleOverlayPlane(dependency_->GetSurfaceHandle(),
overlay.plane_z_order, overlay.transform,
gfx::ToRoundedRect(overlay.display_rect),
overlay.uv_rect, !overlay.is_opaque,
ZxEventsToGpuFences(frame.acquire_fences),
std::move(frame.overlays[i].release_fences));
}
auto now = base::TimeTicks::Now();
auto present_time = now;
// If we have PresentationState frame a previously displayed frame then use it
// to calculate target timestamp for the new frame.
if (presentation_state_) {
uint32_t relative_position =
frame.ordinal - presentation_state_->presented_frame_ordinal;
present_time = presentation_state_->presentation_time +
presentation_state_->interval * relative_position -
base::TimeDelta::FromMilliseconds(1);
present_time = std::max(present_time, now);
}
// Ensure that the target timestamp is not decreasing from the previous frame,
// since Scenic doesn't allow it (see crbug.com/1181528).
present_time = std::max(present_time, last_frame_present_time_);
last_frame_present_time_ = present_time;
image_pipe_->PresentImage(
frame.image_id, present_time.ToZxTime(), std::move(frame.acquire_fences),
std::move(frame.release_fences),
fit::bind_member(this, &OutputPresenterFuchsia::OnPresentComplete));
}
void OutputPresenterFuchsia::OnPresentComplete(
fuchsia::images::PresentationInfo presentation_info) {
TRACE_EVENT_NESTABLE_ASYNC_END1("viz", "OutputPresenterFuchsia::PresentFrame",
TRACE_ID_LOCAL(this), "image_id",
pending_frames_.front().image_id);
auto presentation_time =
base::TimeTicks::FromZxTime(presentation_info.presentation_time);
auto presentation_interval =
base::TimeDelta::FromZxDuration(presentation_info.presentation_interval);
std::move(pending_frames_.front().completion_callback)
.Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_ACK));
std::move(pending_frames_.front().presentation_callback)
.Run(gfx::PresentationFeedback(presentation_time, presentation_interval,
gfx::PresentationFeedback::kVSync));
if (pending_frames_.front().remove_buffer_collection) {
image_pipe_->RemoveBufferCollection(
pending_frames_.front().buffer_collection_id);
}
presentation_state_ =
PresentationState{pending_frames_.front().ordinal, presentation_time,
presentation_interval};
pending_frames_.pop_front();
}
} // namespace viz