| // 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/dri/hardware_display_controller.h" |
| |
| #include <drm.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <xf86drm.h> |
| |
| #include "base/basictypes.h" |
| #include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| #include "base/time/time.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/ozone/platform/dri/crtc_state.h" |
| #include "ui/ozone/platform/dri/dri_buffer.h" |
| #include "ui/ozone/platform/dri/dri_wrapper.h" |
| #include "ui/ozone/public/native_pixmap.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| // DRM callback on page flip events. This callback is triggered after the |
| // page flip has happened and the backbuffer is now the new frontbuffer |
| // The old frontbuffer is no longer used by the hardware and can be used for |
| // future draw operations. |
| // |
| // |device| will contain a reference to the |ScanoutSurface| object which |
| // the event belongs to. |
| // |
| // TODO(dnicoara) When we have a FD handler for the DRM calls in the message |
| // loop, we can move this function in the handler. |
| void HandlePageFlipEvent(int fd, |
| unsigned int frame, |
| unsigned int seconds, |
| unsigned int useconds, |
| void* controller) { |
| static_cast<HardwareDisplayController*>(controller) |
| ->OnPageFlipEvent(frame, seconds, useconds); |
| } |
| |
| const OverlayPlane& GetPrimaryPlane(const OverlayPlaneList& overlays) { |
| for (size_t i = 0; i < overlays.size(); ++i) { |
| if (overlays[i].z_order == 0) |
| return overlays[i]; |
| } |
| |
| NOTREACHED(); |
| return overlays[0]; |
| } |
| |
| } // namespace |
| |
| OverlayPlane::OverlayPlane(scoped_refptr<ScanoutBuffer> buffer) |
| : buffer(buffer), |
| z_order(0), |
| display_bounds(gfx::Point(), buffer->GetSize()), |
| crop_rect(0, 0, 1, 1), |
| overlay_plane(0) {} |
| |
| OverlayPlane::OverlayPlane(scoped_refptr<ScanoutBuffer> buffer, |
| int z_order, |
| gfx::OverlayTransform plane_transform, |
| const gfx::Rect& display_bounds, |
| const gfx::RectF& crop_rect) |
| : buffer(buffer), |
| z_order(z_order), |
| plane_transform(plane_transform), |
| display_bounds(display_bounds), |
| crop_rect(crop_rect), |
| overlay_plane(0) { |
| } |
| |
| OverlayPlane::~OverlayPlane() {} |
| |
| HardwareDisplayController::HardwareDisplayController( |
| DriWrapper* drm, |
| scoped_ptr<CrtcState> state) |
| : drm_(drm), |
| is_disabled_(true), |
| time_of_last_flip_(0), |
| pending_page_flips_(0) { |
| crtc_states_.push_back(state.release()); |
| } |
| |
| HardwareDisplayController::~HardwareDisplayController() { |
| // Reset the cursor. |
| UnsetCursor(); |
| } |
| |
| bool HardwareDisplayController::Modeset(const OverlayPlane& primary, |
| drmModeModeInfo mode) { |
| TRACE_EVENT0("dri", "HDC::Modeset"); |
| DCHECK(primary.buffer.get()); |
| pending_page_flips_ = 0; |
| bool status = true; |
| for (size_t i = 0; i < crtc_states_.size(); ++i) { |
| status &= ModesetCrtc(primary.buffer, mode, crtc_states_[i]); |
| crtc_states_[i]->set_is_disabled(false); |
| } |
| |
| // Since a subset of controllers may be actively using |primary|, just keep |
| // track of it. |
| current_planes_ = std::vector<OverlayPlane>(1, primary); |
| pending_planes_.clear(); |
| is_disabled_ = false; |
| mode_ = mode; |
| return status; |
| } |
| |
| bool HardwareDisplayController::Enable() { |
| TRACE_EVENT0("dri", "HDC::Enable"); |
| DCHECK(!current_planes_.empty()); |
| OverlayPlane primary = GetPrimaryPlane(current_planes_); |
| DCHECK(primary.buffer.get()); |
| pending_page_flips_ = 0; |
| bool status = true; |
| for (size_t i = 0; i < crtc_states_.size(); ++i) |
| status &= ModesetCrtc(primary.buffer, mode_, crtc_states_[i]); |
| |
| return status; |
| } |
| |
| void HardwareDisplayController::Disable() { |
| TRACE_EVENT0("dri", "HDC::Disable"); |
| pending_page_flips_ = 0; |
| for (size_t i = 0; i < crtc_states_.size(); ++i) { |
| drm_->DisableCrtc(crtc_states_[i]->crtc()); |
| crtc_states_[i]->set_is_disabled(true); |
| } |
| |
| is_disabled_ = true; |
| } |
| |
| void HardwareDisplayController::QueueOverlayPlane(const OverlayPlane& plane) { |
| pending_planes_.push_back(plane); |
| } |
| |
| bool HardwareDisplayController::SchedulePageFlip() { |
| DCHECK(!pending_planes_.empty()); |
| DCHECK_EQ(0u, pending_page_flips_); |
| |
| if (is_disabled_) |
| return true; |
| |
| bool status = true; |
| for (size_t i = 0; i < crtc_states_.size(); ++i) |
| status &= SchedulePageFlipOnCrtc(pending_planes_, crtc_states_[i]); |
| |
| return status; |
| } |
| |
| void HardwareDisplayController::WaitForPageFlipEvent() { |
| TRACE_EVENT1("dri", "HDC::WaitForPageFlipEvent", |
| "pending_pageflips", pending_page_flips_); |
| |
| bool has_pending_page_flips = pending_page_flips_ != 0; |
| drmEventContext drm_event; |
| drm_event.version = DRM_EVENT_CONTEXT_VERSION; |
| drm_event.page_flip_handler = HandlePageFlipEvent; |
| drm_event.vblank_handler = NULL; |
| |
| // Wait for the page-flips to complete. |
| while (pending_page_flips_ > 0) |
| drm_->HandleEvent(drm_event); |
| |
| // In case there are no pending pageflips do not replace the current planes |
| // since they are still being used. |
| if (has_pending_page_flips) |
| current_planes_.swap(pending_planes_); |
| |
| pending_planes_.clear(); |
| } |
| |
| void HardwareDisplayController::OnPageFlipEvent(unsigned int frame, |
| unsigned int seconds, |
| unsigned int useconds) { |
| TRACE_EVENT0("dri", "HDC::OnPageFlipEvent"); |
| |
| --pending_page_flips_; |
| time_of_last_flip_ = |
| static_cast<uint64_t>(seconds) * base::Time::kMicrosecondsPerSecond + |
| useconds; |
| } |
| |
| bool HardwareDisplayController::SetCursor(scoped_refptr<ScanoutBuffer> buffer) { |
| bool status = true; |
| cursor_buffer_ = buffer; |
| |
| if (is_disabled_) |
| return true; |
| |
| for (size_t i = 0; i < crtc_states_.size(); ++i) { |
| status &= drm_->SetCursor( |
| crtc_states_[i]->crtc(), buffer->GetHandle(), buffer->GetSize()); |
| } |
| |
| return status; |
| } |
| |
| bool HardwareDisplayController::UnsetCursor() { |
| bool status = true; |
| cursor_buffer_ = NULL; |
| for (size_t i = 0; i < crtc_states_.size(); ++i) |
| status &= drm_->SetCursor(crtc_states_[i]->crtc(), 0, gfx::Size()); |
| |
| return status; |
| } |
| |
| bool HardwareDisplayController::MoveCursor(const gfx::Point& location) { |
| if (is_disabled_) |
| return true; |
| |
| bool status = true; |
| for (size_t i = 0; i < crtc_states_.size(); ++i) |
| status &= drm_->MoveCursor(crtc_states_[i]->crtc(), location); |
| |
| return status; |
| } |
| |
| void HardwareDisplayController::AddCrtc( |
| scoped_ptr<CrtcState> state) { |
| crtc_states_.push_back(state.release()); |
| } |
| |
| scoped_ptr<CrtcState> HardwareDisplayController::RemoveCrtc(uint32_t crtc) { |
| for (ScopedVector<CrtcState>::iterator it = crtc_states_.begin(); |
| it != crtc_states_.end(); |
| ++it) { |
| if ((*it)->crtc() == crtc) { |
| scoped_ptr<CrtcState> controller(*it); |
| crtc_states_.weak_erase(it); |
| return controller.Pass(); |
| } |
| } |
| |
| return scoped_ptr<CrtcState>(); |
| } |
| |
| bool HardwareDisplayController::HasCrtc(uint32_t crtc) const { |
| for (size_t i = 0; i < crtc_states_.size(); ++i) |
| if (crtc_states_[i]->crtc() == crtc) |
| return true; |
| |
| return false; |
| } |
| |
| bool HardwareDisplayController::IsMirrored() const { |
| return crtc_states_.size() > 1; |
| } |
| |
| bool HardwareDisplayController::IsDisabled() const { |
| return is_disabled_; |
| } |
| |
| gfx::Size HardwareDisplayController::GetModeSize() const { |
| return gfx::Size(mode_.hdisplay, mode_.vdisplay); |
| } |
| |
| bool HardwareDisplayController::ModesetCrtc( |
| const scoped_refptr<ScanoutBuffer>& buffer, |
| drmModeModeInfo mode, |
| CrtcState* state) { |
| if (!drm_->SetCrtc(state->crtc(), |
| buffer->GetFramebufferId(), |
| std::vector<uint32_t>(1, state->connector()), |
| &mode)) { |
| LOG(ERROR) << "Failed to modeset: error='" << strerror(errno) |
| << "' crtc=" << state->crtc() |
| << " connector=" << state->connector() |
| << " framebuffer_id=" << buffer->GetFramebufferId() |
| << " mode=" << mode.hdisplay << "x" << mode.vdisplay << "@" |
| << mode.vrefresh; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool HardwareDisplayController::SchedulePageFlipOnCrtc( |
| const OverlayPlaneList& overlays, |
| CrtcState* state) { |
| const OverlayPlane& primary = GetPrimaryPlane(overlays); |
| DCHECK(primary.buffer.get()); |
| |
| if (primary.buffer->GetSize() != gfx::Size(mode_.hdisplay, mode_.vdisplay)) { |
| LOG(WARNING) << "Trying to pageflip a buffer with the wrong size. Expected " |
| << mode_.hdisplay << "x" << mode_.vdisplay |
| << " got " << primary.buffer->GetSize().ToString() << " for" |
| << " crtc=" << state->crtc() |
| << " connector=" << state->connector(); |
| return true; |
| } |
| |
| if (!drm_->PageFlip(state->crtc(), |
| primary.buffer->GetFramebufferId(), |
| this)) { |
| LOG(ERROR) << "Cannot page flip: error='" << strerror(errno) << "'" |
| << " crtc=" << state->crtc() |
| << " framebuffer=" << primary.buffer->GetFramebufferId() |
| << " size=" << primary.buffer->GetSize().ToString(); |
| return false; |
| } |
| |
| ++pending_page_flips_; |
| |
| for (size_t i = 0; i < overlays.size(); i++) { |
| const OverlayPlane& plane = overlays[i]; |
| if (!plane.overlay_plane) |
| continue; |
| |
| const gfx::Size& size = plane.buffer->GetSize(); |
| gfx::RectF crop_rect = plane.crop_rect; |
| crop_rect.Scale(size.width(), size.height()); |
| if (!drm_->PageFlipOverlay(state->crtc(), |
| plane.buffer->GetFramebufferId(), |
| plane.display_bounds, |
| crop_rect, |
| plane.overlay_plane)) { |
| LOG(ERROR) << "Cannot display on overlay: " << strerror(errno); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace ui |