// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/ozone/platform/drm/gpu/gbm_surfaceless.h"
#include <memory>
#include <utility>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "ui/gfx/gpu_fence.h"
#include "ui/gfx/gpu_fence_handle.h"
#include "ui/gfx/presentation_feedback.h"
#include "ui/gl/gl_bindings.h"
#include "ui/ozone/common/egl_util.h"
#include "ui/ozone/platform/drm/gpu/drm_device.h"
#include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
#include "ui/ozone/platform/drm/gpu/drm_window_proxy.h"
#include "ui/ozone/platform/drm/gpu/gbm_surface_factory.h"
#if BUILDFLAG(USE_OPENGL_APITRACE)
#include "ui/gl/gl_implementation.h"
#endif
namespace ui {
namespace {
void WaitForFence(EGLDisplay display, EGLSyncKHR fence) {
eglClientWaitSyncKHR(display, fence, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR,
EGL_FOREVER_KHR);
eglDestroySyncKHR(display, fence);
}
} // namespace
GbmSurfaceless::GbmSurfaceless(GbmSurfaceFactory* surface_factory,
gl::GLDisplayEGL* display,
std::unique_ptr<DrmWindowProxy> window,
gfx::AcceleratedWidget widget)
: surface_factory_(surface_factory),
window_(std::move(window)),
widget_(widget),
display_(display) {
surface_factory_->RegisterSurface(window_->widget(), this);
supports_plane_gpu_fences_ = window_->SupportsGpuFences();
unsubmitted_frames_.push_back(std::make_unique<PendingFrame>());
}
void GbmSurfaceless::QueueOverlayPlane(DrmOverlayPlane plane) {
is_on_external_drm_device_ = !plane.buffer->drm_device()->is_primary_device();
planes_.push_back(std::move(plane));
}
bool GbmSurfaceless::ScheduleOverlayPlane(
gl::OverlayImage image,
std::unique_ptr<gfx::GpuFence> gpu_fence,
const gfx::OverlayPlaneData& overlay_plane_data) {
unsubmitted_frames_.back()->overlays.emplace_back(
std::move(image), std::move(gpu_fence), overlay_plane_data);
return true;
}
bool GbmSurfaceless::Resize(const gfx::Size& size,
float scale_factor,
const gfx::ColorSpace& color_space,
bool has_alpha) {
return true;
}
bool GbmSurfaceless::SupportsPlaneGpuFences() const {
return supports_plane_gpu_fences_;
}
void GbmSurfaceless::Present(SwapCompletionCallback completion_callback,
PresentationCallback presentation_callback,
gfx::FrameData data) {
TRACE_EVENT0("drm", "GbmSurfaceless::Present");
// If last swap failed, don't try to schedule new ones.
if (!last_swap_buffers_result_) {
std::move(completion_callback)
.Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_FAILED));
// Notify the caller, the buffer is never presented on a screen.
std::move(presentation_callback).Run(gfx::PresentationFeedback::Failure());
return;
}
if (!supports_plane_gpu_fences_) {
glFlush();
}
#if BUILDFLAG(USE_OPENGL_APITRACE)
gl::TerminateFrame(); // Notify end of frame at buffer swap request.
#endif
PendingFrame* frame = unsubmitted_frames_.back().get();
frame->completion_callback = std::move(completion_callback);
frame->presentation_callback = std::move(presentation_callback);
unsubmitted_frames_.push_back(std::make_unique<PendingFrame>());
// TODO(dcastagna): Remove the following workaround once we get explicit sync
// on all Intel boards, currently we don't have it on legacy KMS.
// We can not rely on implicit sync on external devices (crbug.com/692508).
// NOTE: When on internal devices, |is_on_external_drm_device_| is set to true
// by default conservatively, and it is correctly computed after the first
// plane is enqueued in QueueOverlayPlane, that is called from
// GbmSurfaceless::SubmitFrame.
// This means |is_on_external_drm_device_| could be incorrectly set to true
// the first time we're testing it.
if (supports_plane_gpu_fences_ ||
(!use_egl_fence_sync_ && !is_on_external_drm_device_)) {
frame->ready = true;
SubmitFrame();
return;
}
// TODO: the following should be replaced by a per surface flush as it gets
// implemented in GL drivers.
EGLSyncKHR fence = InsertFence();
CHECK_NE(fence, EGL_NO_SYNC_KHR) << "eglCreateSyncKHR failed";
base::OnceClosure fence_wait_task =
base::BindOnce(&WaitForFence, GetEGLDisplay(), fence);
base::OnceClosure fence_retired_callback = base::BindOnce(
&GbmSurfaceless::FenceRetired, weak_factory_.GetWeakPtr(), frame);
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
std::move(fence_wait_task), std::move(fence_retired_callback));
}
void GbmSurfaceless::SetRelyOnImplicitSync() {
use_egl_fence_sync_ = false;
}
void GbmSurfaceless::SetNotifyNonSimpleOverlayFailure() {
notify_non_simple_overlay_failure_ = true;
}
GbmSurfaceless::~GbmSurfaceless() {
surface_factory_->UnregisterSurface(window_->widget());
}
GbmSurfaceless::PendingFrame::PendingFrame() = default;
GbmSurfaceless::PendingFrame::~PendingFrame() = default;
bool GbmSurfaceless::PendingFrame::ScheduleOverlayPlanes(
gfx::AcceleratedWidget widget) {
for (auto& overlay : overlays)
if (!overlay.ScheduleOverlayPlane(widget))
return false;
return true;
}
void GbmSurfaceless::SubmitFrame() {
DCHECK(!unsubmitted_frames_.empty());
if (unsubmitted_frames_.front()->ready && !submitted_frame_) {
bool should_handle_non_simple_overlay_failure = false;
for (auto& overlay : unsubmitted_frames_.front()->overlays) {
if (overlay.z_order() == 0 && overlay.gpu_fence()) {
submitted_frame_gpu_fence_ = std::make_unique<gfx::GpuFence>(
overlay.gpu_fence()->GetGpuFenceHandle().Clone());
}
// At the moment, only fullscreen overlays are treated in a special way.
// if other types of overlays also need special handling, then also
// update the DrmOverlayManager, which handles that.
if (overlay.overlay_type() == gfx::OverlayType::kFullScreen &&
should_handle_non_simple_overlay_failure) {
should_handle_non_simple_overlay_failure = true;
break;
}
}
submitted_frame_ = std::move(unsubmitted_frames_.front());
unsubmitted_frames_.erase(unsubmitted_frames_.begin());
bool schedule_planes_succeeded =
submitted_frame_->ScheduleOverlayPlanes(widget_);
if (!schedule_planes_succeeded) {
OnSubmission(should_handle_non_simple_overlay_failure,
gfx::SwapResult::SWAP_FAILED,
/*release_fence=*/gfx::GpuFenceHandle());
OnPresentation(gfx::PresentationFeedback::Failure());
return;
}
window_->SchedulePageFlip(
std::move(planes_),
base::BindOnce(&GbmSurfaceless::OnSubmission,
weak_factory_.GetWeakPtr(),
should_handle_non_simple_overlay_failure),
base::BindOnce(&GbmSurfaceless::OnPresentation,
weak_factory_.GetWeakPtr()));
planes_.clear();
}
}
EGLSyncKHR GbmSurfaceless::InsertFence() {
const bool has_global_fence = display_->ext->b_EGL_ANGLE_global_fence_sync;
const bool has_implicit_external_fence =
display_->ext->b_EGL_ARM_implicit_external_sync;
// Prefer EGL_ANGLE_global_fence_sync as it guarantees synchronization with
// past submissions from all contexts, rather than the current context.
const EGLenum syncType =
has_global_fence ? EGL_SYNC_GLOBAL_FENCE_ANGLE : EGL_SYNC_FENCE_KHR;
const bool use_implicit_external_sync =
has_implicit_external_fence && !has_global_fence;
const EGLint attrib_list[] = {EGL_SYNC_CONDITION_KHR,
EGL_SYNC_PRIOR_COMMANDS_IMPLICIT_EXTERNAL_ARM,
EGL_NONE};
return eglCreateSyncKHR(GetEGLDisplay(), syncType,
use_implicit_external_sync ? attrib_list : nullptr);
}
void GbmSurfaceless::FenceRetired(PendingFrame* frame) {
frame->ready = true;
SubmitFrame();
}
void GbmSurfaceless::OnSubmission(bool should_handle_fullscreen_overlay_failure,
gfx::SwapResult result,
gfx::GpuFenceHandle release_fence) {
// Handling fullscreen overlays' failures means usage of the
// gfx::SwapResult::SWAP_NON_SIMPLE_OVERLAYS_FAILED. That way, viz is able to
// recover from the error. The gpu watchdog will reset as viz will either
// reschedule the same frame or it'll send a new one if it's already queued.
if (should_handle_fullscreen_overlay_failure &&
result == gfx::SwapResult::SWAP_FAILED) {
result = gfx::SwapResult::SWAP_NON_SIMPLE_OVERLAYS_FAILED;
}
submitted_frame_->swap_result = result;
if (!release_fence.is_null()) {
std::move(submitted_frame_->completion_callback)
.Run(gfx::SwapCompletionResult(result, std::move(release_fence)));
}
}
void GbmSurfaceless::OnPresentation(const gfx::PresentationFeedback& feedback) {
gfx::PresentationFeedback feedback_copy = feedback;
if (submitted_frame_gpu_fence_ && !feedback.failed()) {
feedback_copy.ready_timestamp =
submitted_frame_gpu_fence_->GetMaxTimestamp();
}
submitted_frame_gpu_fence_.reset();
submitted_frame_->overlays.clear();
gfx::SwapResult result = submitted_frame_->swap_result;
if (submitted_frame_->completion_callback)
std::move(submitted_frame_->completion_callback)
.Run(gfx::SwapCompletionResult(result));
std::move(submitted_frame_->presentation_callback).Run(feedback_copy);
submitted_frame_.reset();
if (result == gfx::SwapResult::SWAP_FAILED) {
last_swap_buffers_result_ = false;
return;
}
SubmitFrame();
}
EGLDisplay GbmSurfaceless::GetEGLDisplay() {
return display_->GetDisplay();
}
} // namespace ui