| // Copyright 2014 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 "ui/ozone/platform/drm/gpu/hardware_display_controller.h" |
| |
| #include <drm.h> |
| #include <string.h> |
| #include <xf86drm.h> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/stl_util.h" |
| #include "base/syslog_logging.h" |
| #include "base/trace_event/trace_event.h" |
| #include "third_party/libdrm/src/include/drm/drm_fourcc.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "third_party/skia/include/core/SkImage.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/gpu_fence_handle.h" |
| #include "ui/gfx/linux/drm_util_linux.h" |
| #include "ui/gfx/native_pixmap.h" |
| #include "ui/gfx/presentation_feedback.h" |
| #include "ui/gfx/swap_result.h" |
| #include "ui/ozone/platform/drm/common/drm_util.h" |
| #include "ui/ozone/platform/drm/gpu/crtc_controller.h" |
| #include "ui/ozone/platform/drm/gpu/drm_device.h" |
| #include "ui/ozone/platform/drm/gpu/drm_dumb_buffer.h" |
| #include "ui/ozone/platform/drm/gpu/hardware_display_plane.h" |
| #include "ui/ozone/platform/drm/gpu/page_flip_request.h" |
| |
| // Vendor ID for downstream, interim ChromeOS specific modifiers. |
| #define DRM_FORMAT_MOD_VENDOR_CHROMEOS 0xf0 |
| // TODO(gurchetansingh) Remove once DRM_FORMAT_MOD_ARM_AFBC is used by all |
| // kernels and allocators. |
| #define DRM_FORMAT_MOD_CHROMEOS_ROCKCHIP_AFBC fourcc_mod_code(CHROMEOS, 1) |
| |
| namespace ui { |
| |
| namespace { |
| |
| void CompletePageFlip( |
| base::WeakPtr<HardwareDisplayController> hardware_display_controller_, |
| int modeset_sequence, |
| PresentationOnceCallback callback, |
| DrmOverlayPlaneList plane_list, |
| const gfx::PresentationFeedback& presentation_feedback) { |
| if (hardware_display_controller_) { |
| hardware_display_controller_->OnPageFlipComplete( |
| modeset_sequence, std::move(plane_list), presentation_feedback); |
| } |
| std::move(callback).Run(presentation_feedback); |
| } |
| |
| void DrawCursor(DrmDumbBuffer* cursor, const SkBitmap& image) { |
| SkRect damage; |
| image.getBounds(&damage); |
| |
| // Clear to transparent in case |image| is smaller than the canvas. |
| SkCanvas* canvas = cursor->GetCanvas(); |
| canvas->clear(SK_ColorTRANSPARENT); |
| canvas->drawImageRect(image.asImage(), damage, SkSamplingOptions()); |
| } |
| |
| } // namespace |
| |
| HardwareDisplayController::HardwareDisplayController( |
| std::unique_ptr<CrtcController> controller, |
| const gfx::Point& origin) |
| : origin_(origin) { |
| AddCrtc(std::move(controller)); |
| AllocateCursorBuffers(); |
| } |
| |
| HardwareDisplayController::~HardwareDisplayController() = default; |
| |
| void HardwareDisplayController::GetModesetProps( |
| CommitRequest* commit_request, |
| const DrmOverlayPlaneList& modeset_planes, |
| const drmModeModeInfo& mode) { |
| GetModesetPropsForCrtcs(commit_request, modeset_planes, |
| /*use_current_crtc_mode=*/false, mode); |
| } |
| |
| void HardwareDisplayController::GetEnableProps( |
| CommitRequest* commit_request, |
| const DrmOverlayPlaneList& modeset_planes) { |
| // TODO(markyacoub): Simplify and remove the use of empty_mode. |
| drmModeModeInfo empty_mode = {}; |
| GetModesetPropsForCrtcs(commit_request, modeset_planes, |
| /*use_current_crtc_mode=*/true, empty_mode); |
| } |
| |
| void HardwareDisplayController::GetModesetPropsForCrtcs( |
| CommitRequest* commit_request, |
| const DrmOverlayPlaneList& modeset_planes, |
| bool use_current_crtc_mode, |
| const drmModeModeInfo& mode) { |
| DCHECK(commit_request); |
| |
| GetDrmDevice()->plane_manager()->BeginFrame(&owned_hardware_planes_); |
| |
| for (const auto& controller : crtc_controllers_) { |
| drmModeModeInfo modeset_mode = |
| use_current_crtc_mode ? controller->mode() : mode; |
| |
| DrmOverlayPlaneList overlays = DrmOverlayPlane::Clone(modeset_planes); |
| |
| CrtcCommitRequest request = CrtcCommitRequest::EnableCrtcRequest( |
| controller->crtc(), controller->connector(), modeset_mode, origin_, |
| &owned_hardware_planes_, std::move(overlays)); |
| commit_request->push_back(std::move(request)); |
| } |
| } |
| |
| void HardwareDisplayController::GetDisableProps(CommitRequest* commit_request) { |
| for (const auto& controller : crtc_controllers_) { |
| CrtcCommitRequest request = CrtcCommitRequest::DisableCrtcRequest( |
| controller->crtc(), controller->connector(), &owned_hardware_planes_); |
| commit_request->push_back(std::move(request)); |
| } |
| } |
| |
| void HardwareDisplayController::UpdateState( |
| const CrtcCommitRequest& crtc_request) { |
| if (crash_gpu_timer_.IsRunning()) { |
| crash_gpu_timer_.AbandonAndStop(); |
| SYSLOG(INFO) |
| << "Detected a modeset attempt after " << failed_page_flip_counter_ |
| << " failed page flips. Aborting GPU process self-destruct with " |
| << crash_gpu_timer_.desired_run_time() - base::TimeTicks::Now() |
| << " to spare."; |
| failed_page_flip_counter_ = 0; |
| } |
| |
| // Verify that the current state matches the requested state. |
| if (crtc_request.should_enable() && IsEnabled()) { |
| DCHECK(!crtc_request.overlays().empty()); |
| // TODO(markyacoub): This should be absorbed in the commit request. |
| ResetCursor(); |
| OnModesetComplete(crtc_request.overlays()); |
| } |
| } |
| |
| void HardwareDisplayController::SchedulePageFlip( |
| DrmOverlayPlaneList plane_list, |
| SwapCompletionOnceCallback submission_callback, |
| PresentationOnceCallback presentation_callback) { |
| DCHECK(!page_flip_request_); |
| scoped_refptr<PageFlipRequest> page_flip_request = |
| base::MakeRefCounted<PageFlipRequest>(GetRefreshInterval()); |
| gfx::GpuFenceHandle release_fence; |
| |
| bool status = |
| ScheduleOrTestPageFlip(plane_list, page_flip_request, &release_fence); |
| if (!status) { |
| for (const auto& plane : plane_list) { |
| // If the page flip failed and we see that the buffer has been allocated |
| // before the latest modeset, it could mean it was an in-flight buffer |
| // carrying an obsolete configuration. |
| // Request a buffer reallocation to reflect the new change. |
| if (plane.buffer && |
| plane.buffer->modeset_sequence_id_at_allocation() < |
| plane.buffer->drm_device()->modeset_sequence_id()) { |
| std::move(submission_callback) |
| .Run(gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS, |
| /*release_fence=*/gfx::GpuFenceHandle()); |
| std::move(presentation_callback) |
| .Run(gfx::PresentationFeedback::Failure()); |
| return; |
| } |
| } |
| |
| // No outdated buffers detected which makes this a true page flip failure. |
| // Start the GPU self-destruct timer if needed and report the failure. |
| failed_page_flip_counter_++; |
| if (!crash_gpu_timer_.IsRunning()) { |
| DCHECK_EQ(1, failed_page_flip_counter_); |
| LOG(WARNING) << "Initiating GPU process self-destruct in " |
| << kWaitForModesetTimeout |
| << " unless a modeset attempt is detected."; |
| |
| crash_gpu_timer_.Start( |
| FROM_HERE, kWaitForModesetTimeout, base::BindOnce([] { |
| LOG(FATAL) << "Failed to modeset within " << kWaitForModesetTimeout |
| << " of the first page flip failure. Crashing GPU " |
| "process. Goodbye."; |
| })); |
| } |
| |
| std::move(submission_callback) |
| .Run(gfx::SwapResult::SWAP_FAILED, |
| /*release_fence=*/gfx::GpuFenceHandle()); |
| std::move(presentation_callback).Run(gfx::PresentationFeedback::Failure()); |
| return; |
| } |
| if (page_flip_request->page_flip_count() == 0) { |
| // Apparently, there was nothing to do. This probably should not be |
| // able to happen but both CrtcController::AssignOverlayPlanes and |
| // HardwareDisplayPlaneManagerLegacy::Commit appear to have cases |
| // where we ACK without actually scheduling a page flip. |
| std::move(submission_callback) |
| .Run(gfx::SwapResult::SWAP_ACK, |
| /*release_fence=*/gfx::GpuFenceHandle()); |
| std::move(presentation_callback).Run(gfx::PresentationFeedback::Failure()); |
| return; |
| } |
| |
| std::move(submission_callback) |
| .Run(gfx::SwapResult::SWAP_ACK, std::move(release_fence)); |
| |
| // Everything was submitted successfully, wait for asynchronous completion. |
| page_flip_request->TakeCallback( |
| base::BindOnce(&CompletePageFlip, weak_ptr_factory_.GetWeakPtr(), |
| GetDrmDevice()->modeset_sequence_id(), |
| std::move(presentation_callback), std::move(plane_list))); |
| page_flip_request_ = std::move(page_flip_request); |
| } |
| |
| bool HardwareDisplayController::TestPageFlip( |
| const DrmOverlayPlaneList& plane_list) { |
| return ScheduleOrTestPageFlip(plane_list, nullptr, nullptr); |
| } |
| |
| bool HardwareDisplayController::ScheduleOrTestPageFlip( |
| const DrmOverlayPlaneList& plane_list, |
| scoped_refptr<PageFlipRequest> page_flip_request, |
| gfx::GpuFenceHandle* release_fence) { |
| TRACE_EVENT0("drm", "HDC::SchedulePageFlip"); |
| DCHECK(IsEnabled()); |
| |
| // Ignore requests with no planes to schedule. |
| if (plane_list.empty()) |
| return true; |
| |
| DrmOverlayPlaneList pending_planes = DrmOverlayPlane::Clone(plane_list); |
| std::sort(pending_planes.begin(), pending_planes.end(), |
| [](const DrmOverlayPlane& l, const DrmOverlayPlane& r) { |
| return l.z_order < r.z_order; |
| }); |
| GetDrmDevice()->plane_manager()->BeginFrame(&owned_hardware_planes_); |
| |
| bool status = true; |
| for (const auto& controller : crtc_controllers_) { |
| status &= controller->AssignOverlayPlanes( |
| &owned_hardware_planes_, pending_planes, /*is_modesetting=*/false); |
| } |
| |
| status &= GetDrmDevice()->plane_manager()->Commit( |
| &owned_hardware_planes_, page_flip_request, release_fence); |
| |
| return status; |
| } |
| |
| std::vector<uint64_t> HardwareDisplayController::GetFormatModifiers( |
| uint32_t fourcc_format) const { |
| if (crtc_controllers_.empty()) |
| return std::vector<uint64_t>(); |
| |
| std::vector<uint64_t> modifiers = |
| crtc_controllers_[0]->GetFormatModifiers(fourcc_format); |
| |
| for (size_t i = 1; i < crtc_controllers_.size(); ++i) { |
| std::vector<uint64_t> other = |
| crtc_controllers_[i]->GetFormatModifiers(fourcc_format); |
| std::vector<uint64_t> intersection; |
| |
| std::set_intersection(modifiers.begin(), modifiers.end(), other.begin(), |
| other.end(), std::back_inserter(intersection)); |
| modifiers = std::move(intersection); |
| } |
| |
| return modifiers; |
| } |
| |
| std::vector<uint64_t> HardwareDisplayController::GetSupportedModifiers( |
| uint32_t fourcc_format, |
| bool is_modeset) const { |
| if (preferred_format_modifier_.empty()) |
| return std::vector<uint64_t>(); |
| |
| auto it = preferred_format_modifier_.find(fourcc_format); |
| if (it != preferred_format_modifier_.end()) { |
| uint64_t supported_modifier = it->second; |
| // AFBC for modeset buffers doesn't work correctly, as we can't fill it with |
| // a valid AFBC buffer (crbug.com/852675). |
| // For now, don't use AFBC for modeset buffers. |
| if (is_modeset && |
| supported_modifier == DRM_FORMAT_MOD_CHROMEOS_ROCKCHIP_AFBC) { |
| supported_modifier = DRM_FORMAT_MOD_LINEAR; |
| } |
| return std::vector<uint64_t>{supported_modifier}; |
| } |
| |
| return GetFormatModifiers(fourcc_format); |
| } |
| |
| std::vector<uint64_t> |
| HardwareDisplayController::GetFormatModifiersForTestModeset( |
| uint32_t fourcc_format) { |
| // If we're about to test, clear the current preferred modifier. |
| preferred_format_modifier_.clear(); |
| return GetFormatModifiers(fourcc_format); |
| } |
| |
| void HardwareDisplayController::UpdatePreferredModiferForFormat( |
| gfx::BufferFormat buffer_format, |
| uint64_t modifier) { |
| uint32_t fourcc_format = GetFourCCFormatFromBufferFormat(buffer_format); |
| base::InsertOrAssign(preferred_format_modifier_, fourcc_format, modifier); |
| |
| uint32_t opaque_fourcc_format = |
| GetFourCCFormatForOpaqueFramebuffer(buffer_format); |
| base::InsertOrAssign(preferred_format_modifier_, opaque_fourcc_format, |
| modifier); |
| } |
| |
| void HardwareDisplayController::MoveCursor(const gfx::Point& location) { |
| cursor_location_ = location; |
| UpdateCursorLocation(); |
| } |
| |
| void HardwareDisplayController::SetCursor(SkBitmap bitmap) { |
| if (bitmap.drawsNothing()) { |
| current_cursor_ = nullptr; |
| } else { |
| current_cursor_ = NextCursorBuffer(); |
| DrawCursor(current_cursor_, bitmap); |
| } |
| |
| UpdateCursorImage(); |
| } |
| |
| void HardwareDisplayController::AddCrtc( |
| std::unique_ptr<CrtcController> controller) { |
| scoped_refptr<DrmDevice> drm = controller->drm(); |
| DCHECK(crtc_controllers_.empty() || drm == GetDrmDevice()); |
| |
| // Check if this controller owns any planes and ensure we keep track of them. |
| const std::vector<std::unique_ptr<HardwareDisplayPlane>>& all_planes = |
| drm->plane_manager()->planes(); |
| uint32_t crtc = controller->crtc(); |
| for (const auto& plane : all_planes) { |
| if (plane->in_use() && (plane->owning_crtc() == crtc)) |
| owned_hardware_planes_.old_plane_list.push_back(plane.get()); |
| } |
| |
| crtc_controllers_.push_back(std::move(controller)); |
| } |
| |
| std::unique_ptr<CrtcController> HardwareDisplayController::RemoveCrtc( |
| const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc) { |
| auto controller_it = std::find_if( |
| crtc_controllers_.begin(), crtc_controllers_.end(), |
| [drm, crtc](const std::unique_ptr<CrtcController>& crtc_controller) { |
| return crtc_controller->drm() == drm && crtc_controller->crtc() == crtc; |
| }); |
| if (controller_it == crtc_controllers_.end()) |
| return nullptr; |
| |
| std::unique_ptr<CrtcController> controller(std::move(*controller_it)); |
| crtc_controllers_.erase(controller_it); |
| |
| // Move all the planes that have been committed in the last pageflip for this |
| // CRTC at the end of the collection. |
| auto first_plane_to_disable_it = |
| std::partition(owned_hardware_planes_.old_plane_list.begin(), |
| owned_hardware_planes_.old_plane_list.end(), |
| [crtc](const HardwareDisplayPlane* plane) { |
| return plane->owning_crtc() != crtc; |
| }); |
| |
| // Disable the planes enabled with the last commit on |crtc|, otherwise |
| // the planes will be visible if the crtc is reassigned to another connector. |
| HardwareDisplayPlaneList hardware_plane_list; |
| std::copy(first_plane_to_disable_it, |
| owned_hardware_planes_.old_plane_list.end(), |
| std::back_inserter(hardware_plane_list.old_plane_list)); |
| drm->plane_manager()->DisableOverlayPlanes(&hardware_plane_list); |
| |
| // Remove the planes assigned to |crtc|. |
| owned_hardware_planes_.old_plane_list.erase( |
| first_plane_to_disable_it, owned_hardware_planes_.old_plane_list.end()); |
| |
| return controller; |
| } |
| |
| bool HardwareDisplayController::HasCrtc(const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc) const { |
| for (const auto& controller : crtc_controllers_) { |
| if (controller->drm() == drm && controller->crtc() == crtc) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool HardwareDisplayController::IsMirrored() const { |
| return crtc_controllers_.size() > 1; |
| } |
| |
| bool HardwareDisplayController::IsEnabled() const { |
| bool is_enabled = true; |
| |
| for (const auto& controller : crtc_controllers_) |
| is_enabled &= controller->is_enabled(); |
| |
| return is_enabled; |
| } |
| |
| gfx::Size HardwareDisplayController::GetModeSize() const { |
| // If there are multiple CRTCs they should all have the same size. |
| return gfx::Size(crtc_controllers_[0]->mode().hdisplay, |
| crtc_controllers_[0]->mode().vdisplay); |
| } |
| |
| base::TimeDelta HardwareDisplayController::GetRefreshInterval() const { |
| // If there are multiple CRTCs they should all have the same refresh rate. |
| float vrefresh = ModeRefreshRate(crtc_controllers_[0]->mode()); |
| return vrefresh ? base::Seconds(1) / vrefresh : base::TimeDelta(); |
| } |
| |
| base::TimeTicks HardwareDisplayController::GetTimeOfLastFlip() const { |
| return time_of_last_flip_; |
| } |
| |
| scoped_refptr<DrmDevice> HardwareDisplayController::GetDrmDevice() const { |
| DCHECK(!crtc_controllers_.empty()); |
| // TODO(dnicoara) When we support mirroring across DRM devices, figure out |
| // which device should be used for allocations. |
| return crtc_controllers_[0]->drm(); |
| } |
| |
| void HardwareDisplayController::OnPageFlipComplete( |
| int modeset_sequence, |
| DrmOverlayPlaneList pending_planes, |
| const gfx::PresentationFeedback& presentation_feedback) { |
| if (!page_flip_request_) |
| return; // Modeset occured during this page flip. |
| |
| time_of_last_flip_ = presentation_feedback.timestamp; |
| current_planes_ = std::move(pending_planes); |
| |
| for (const auto& controller : crtc_controllers_) { |
| // Only reset the modeset buffer of the crtcs for pageflips that were |
| // committed after the modeset. |
| if (modeset_sequence == GetDrmDevice()->modeset_sequence_id()) { |
| GetDrmDevice()->plane_manager()->ResetModesetStateForCrtc( |
| controller->crtc()); |
| } |
| } |
| page_flip_request_ = nullptr; |
| } |
| |
| void HardwareDisplayController::OnModesetComplete( |
| const DrmOverlayPlaneList& modeset_planes) { |
| // Modesetting is blocking so it has an immediate effect. We can assume that |
| // the current planes have been updated. However, if a page flip is still |
| // pending, set the pending planes to the same values so that the callback |
| // keeps the correct state. |
| page_flip_request_ = nullptr; |
| owned_hardware_planes_.legacy_page_flips.clear(); |
| current_planes_ = DrmOverlayPlane::Clone(modeset_planes); |
| time_of_last_flip_ = base::TimeTicks::Now(); |
| } |
| |
| void HardwareDisplayController::AllocateCursorBuffers() { |
| TRACE_EVENT0("drm", "HDC::AllocateCursorBuffers"); |
| gfx::Size max_cursor_size = GetMaximumCursorSize(GetDrmDevice()->get_fd()); |
| SkImageInfo info = SkImageInfo::MakeN32Premul(max_cursor_size.width(), |
| max_cursor_size.height()); |
| for (size_t i = 0; i < base::size(cursor_buffers_); ++i) { |
| cursor_buffers_[i] = std::make_unique<DrmDumbBuffer>(GetDrmDevice()); |
| // Don't register a framebuffer for cursors since they are special (they |
| // aren't modesetting buffers and drivers may fail to register them due to |
| // their small sizes). |
| if (!cursor_buffers_[i]->Initialize(info)) { |
| LOG(FATAL) << "Failed to initialize cursor buffer"; |
| return; |
| } |
| } |
| } |
| |
| DrmDumbBuffer* HardwareDisplayController::NextCursorBuffer() { |
| ++cursor_frontbuffer_; |
| cursor_frontbuffer_ %= base::size(cursor_buffers_); |
| return cursor_buffers_[cursor_frontbuffer_].get(); |
| } |
| |
| void HardwareDisplayController::UpdateCursorImage() { |
| uint32_t handle = 0; |
| gfx::Size size; |
| |
| if (current_cursor_) { |
| handle = current_cursor_->GetHandle(); |
| size = current_cursor_->GetSize(); |
| } |
| |
| for (const auto& controller : crtc_controllers_) |
| controller->SetCursor(handle, size); |
| } |
| |
| void HardwareDisplayController::UpdateCursorLocation() { |
| for (const auto& controller : crtc_controllers_) |
| controller->MoveCursor(cursor_location_); |
| } |
| |
| void HardwareDisplayController::ResetCursor() { |
| UpdateCursorLocation(); |
| UpdateCursorImage(); |
| } |
| |
| } // namespace ui |