| // 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/screen_manager.h" |
| |
| #include <xf86drmMode.h> |
| |
| #include <utility> |
| |
| #include "base/files/platform_file.h" |
| #include "third_party/skia/include/core/SkCanvas.h" |
| #include "ui/display/types/display_snapshot.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/gpu_fence.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_console_buffer.h" |
| #include "ui/ozone/platform/drm/gpu/drm_device.h" |
| #include "ui/ozone/platform/drm/gpu/drm_window.h" |
| #include "ui/ozone/platform/drm/gpu/hardware_display_controller.h" |
| #include "ui/ozone/platform/drm/gpu/scanout_buffer.h" |
| #include "ui/ozone/platform/drm/gpu/scanout_buffer_generator.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| // Copies the contents of the saved framebuffer from the CRTCs in |controller| |
| // to the new modeset buffer |buffer|. |
| void FillModesetBuffer(const scoped_refptr<DrmDevice>& drm, |
| HardwareDisplayController* controller, |
| ScanoutBuffer* buffer) { |
| DrmConsoleBuffer modeset_buffer(drm, buffer->GetOpaqueFramebufferId()); |
| if (!modeset_buffer.Initialize()) { |
| VLOG(2) << "Failed to grab framebuffer " << buffer->GetOpaqueFramebufferId(); |
| return; |
| } |
| |
| DCHECK(!controller->crtc_controllers().empty()); |
| CrtcController* first_crtc = controller->crtc_controllers()[0].get(); |
| ScopedDrmCrtcPtr saved_crtc(drm->GetCrtc(first_crtc->crtc())); |
| if (!saved_crtc || !saved_crtc->buffer_id) { |
| VLOG(2) << "Crtc has no saved state or wasn't modeset"; |
| return; |
| } |
| |
| uint32_t fourcc_format = buffer->GetFramebufferPixelFormat(); |
| const auto& modifiers = controller->GetFormatModifiers(fourcc_format); |
| for (const uint64_t modifier : modifiers) { |
| // A value of 0 means DRM_FORMAT_MOD_NONE. If the CRTC has any other |
| // modifier (tiling, compression, etc.) we can't read the fb and assume it's |
| // a linear buffer. |
| if (modifier) { |
| VLOG(2) << "Crtc has a modifier and we might not know how to interpret " |
| "the fb."; |
| return; |
| } |
| } |
| |
| // If the display controller is in mirror mode, the CRTCs should be sharing |
| // the same framebuffer. |
| DrmConsoleBuffer saved_buffer(drm, saved_crtc->buffer_id); |
| if (!saved_buffer.Initialize()) { |
| VLOG(2) << "Failed to grab saved framebuffer " << saved_crtc->buffer_id; |
| return; |
| } |
| |
| // Don't copy anything if the sizes mismatch. This can happen when the user |
| // changes modes. |
| if (saved_buffer.canvas()->getBaseLayerSize() != |
| modeset_buffer.canvas()->getBaseLayerSize()) { |
| VLOG(2) << "Previous buffer has a different size than modeset buffer"; |
| return; |
| } |
| |
| SkPaint paint; |
| // Copy the source buffer. Do not perform any blending. |
| paint.setBlendMode(SkBlendMode::kSrc); |
| modeset_buffer.canvas()->drawImage(saved_buffer.image(), 0, 0, &paint); |
| } |
| |
| CrtcController* GetCrtcController(HardwareDisplayController* controller, |
| const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc) { |
| for (const auto& crtc_controller : controller->crtc_controllers()) { |
| if (crtc_controller->crtc() == crtc) |
| return crtc_controller.get(); |
| } |
| |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| ScreenManager::ScreenManager(ScanoutBufferGenerator* buffer_generator) |
| : buffer_generator_(buffer_generator) { |
| } |
| |
| ScreenManager::~ScreenManager() { |
| DCHECK(window_map_.empty()); |
| } |
| |
| void ScreenManager::AddDisplayController(const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc, |
| uint32_t connector) { |
| HardwareDisplayControllers::iterator it = FindDisplayController(drm, crtc); |
| // TODO(dnicoara): Turn this into a DCHECK when async display configuration is |
| // properly supported. (When there can't be a race between forcing initial |
| // display configuration in ScreenManager and display::NativeDisplayDelegate |
| // creating the display controllers.) |
| if (it != controllers_.end()) { |
| LOG(WARNING) << "Display controller (crtc=" << crtc << ") already present."; |
| return; |
| } |
| |
| controllers_.push_back(std::make_unique<HardwareDisplayController>( |
| std::unique_ptr<CrtcController>(new CrtcController(drm, crtc, connector)), |
| gfx::Point())); |
| } |
| |
| void ScreenManager::RemoveDisplayController(const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc) { |
| HardwareDisplayControllers::iterator it = FindDisplayController(drm, crtc); |
| if (it != controllers_.end()) { |
| bool is_mirrored = (*it)->IsMirrored(); |
| (*it)->RemoveCrtc(drm, crtc); |
| if (!is_mirrored) { |
| controllers_.erase(it); |
| UpdateControllerToWindowMapping(); |
| } |
| } |
| } |
| |
| bool ScreenManager::ConfigureDisplayController( |
| const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc, |
| uint32_t connector, |
| const gfx::Point& origin, |
| const drmModeModeInfo& mode) { |
| bool status = |
| ActualConfigureDisplayController(drm, crtc, connector, origin, mode); |
| if (status) |
| UpdateControllerToWindowMapping(); |
| |
| return status; |
| } |
| |
| bool ScreenManager::ActualConfigureDisplayController( |
| const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc, |
| uint32_t connector, |
| const gfx::Point& origin, |
| const drmModeModeInfo& mode) { |
| gfx::Rect modeset_bounds(origin.x(), origin.y(), mode.hdisplay, |
| mode.vdisplay); |
| HardwareDisplayControllers::iterator it = FindDisplayController(drm, crtc); |
| DCHECK(controllers_.end() != it) << "Display controller (crtc=" << crtc |
| << ") doesn't exist."; |
| |
| HardwareDisplayController* controller = it->get(); |
| CrtcController* crtc_controller = GetCrtcController(controller, drm, crtc); |
| // If nothing changed just enable the controller. Note, we perform an exact |
| // comparison on the mode since the refresh rate may have changed. |
| if (SameMode(mode, crtc_controller->mode()) && |
| origin == controller->origin()) { |
| if (controller->IsDisabled()) { |
| HardwareDisplayControllers::iterator mirror = |
| FindActiveDisplayControllerByLocation(drm, modeset_bounds); |
| // If there is an active controller at the same location then start mirror |
| // mode. |
| if (mirror != controllers_.end()) |
| return HandleMirrorMode(it, mirror, drm, crtc, connector, mode); |
| } |
| |
| // Just re-enable the controller to re-use the current state. |
| return EnableController(controller); |
| } |
| |
| // Either the mode or the location of the display changed, so exit mirror |
| // mode and configure the display independently. If the caller still wants |
| // mirror mode, subsequent calls configuring the other controllers will |
| // restore mirror mode. |
| if (controller->IsMirrored()) { |
| controllers_.push_back(std::make_unique<HardwareDisplayController>( |
| controller->RemoveCrtc(drm, crtc), controller->origin())); |
| it = controllers_.end() - 1; |
| controller = it->get(); |
| } |
| |
| HardwareDisplayControllers::iterator mirror = |
| FindActiveDisplayControllerByLocation(drm, modeset_bounds); |
| // Handle mirror mode. |
| if (mirror != controllers_.end() && it != mirror) |
| return HandleMirrorMode(it, mirror, drm, crtc, connector, mode); |
| |
| return ModesetController(controller, origin, mode); |
| } |
| |
| bool ScreenManager::DisableDisplayController( |
| const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc) { |
| HardwareDisplayControllers::iterator it = FindDisplayController(drm, crtc); |
| if (it != controllers_.end()) { |
| HardwareDisplayController* controller = it->get(); |
| if (controller->IsMirrored()) { |
| controllers_.push_back(std::make_unique<HardwareDisplayController>( |
| controller->RemoveCrtc(drm, crtc), controller->origin())); |
| controller = controllers_.back().get(); |
| } |
| |
| controller->Disable(); |
| UpdateControllerToWindowMapping(); |
| return true; |
| } |
| |
| LOG(ERROR) << "Failed to find display controller crtc=" << crtc; |
| return false; |
| } |
| |
| HardwareDisplayController* ScreenManager::GetDisplayController( |
| const gfx::Rect& bounds) { |
| HardwareDisplayControllers::iterator it = |
| FindActiveDisplayControllerByLocation(bounds); |
| if (it != controllers_.end()) |
| return it->get(); |
| |
| return nullptr; |
| } |
| |
| void ScreenManager::AddWindow(gfx::AcceleratedWidget widget, |
| std::unique_ptr<DrmWindow> window) { |
| std::pair<WidgetToWindowMap::iterator, bool> result = |
| window_map_.insert(std::make_pair(widget, std::move(window))); |
| DCHECK(result.second) << "Window already added."; |
| UpdateControllerToWindowMapping(); |
| } |
| |
| std::unique_ptr<DrmWindow> ScreenManager::RemoveWindow( |
| gfx::AcceleratedWidget widget) { |
| std::unique_ptr<DrmWindow> window = std::move(window_map_[widget]); |
| window_map_.erase(widget); |
| DCHECK(window) << "Attempting to remove non-existing window for " << widget; |
| UpdateControllerToWindowMapping(); |
| return window; |
| } |
| |
| DrmWindow* ScreenManager::GetWindow(gfx::AcceleratedWidget widget) { |
| WidgetToWindowMap::iterator it = window_map_.find(widget); |
| if (it != window_map_.end()) |
| return it->second.get(); |
| |
| return nullptr; |
| } |
| |
| ScreenManager::HardwareDisplayControllers::iterator |
| ScreenManager::FindDisplayController(const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc) { |
| for (auto it = controllers_.begin(); it != controllers_.end(); ++it) { |
| if ((*it)->HasCrtc(drm, crtc)) |
| return it; |
| } |
| |
| return controllers_.end(); |
| } |
| |
| ScreenManager::HardwareDisplayControllers::iterator |
| ScreenManager::FindActiveDisplayControllerByLocation(const gfx::Rect& bounds) { |
| for (auto it = controllers_.begin(); it != controllers_.end(); ++it) { |
| gfx::Rect controller_bounds((*it)->origin(), (*it)->GetModeSize()); |
| if (controller_bounds == bounds && !(*it)->IsDisabled()) |
| return it; |
| } |
| |
| return controllers_.end(); |
| } |
| |
| ScreenManager::HardwareDisplayControllers::iterator |
| ScreenManager::FindActiveDisplayControllerByLocation( |
| const scoped_refptr<DrmDevice>& drm, |
| const gfx::Rect& bounds) { |
| for (auto it = controllers_.begin(); it != controllers_.end(); ++it) { |
| gfx::Rect controller_bounds((*it)->origin(), (*it)->GetModeSize()); |
| if ((*it)->GetDrmDevice() == drm && controller_bounds == bounds && |
| !(*it)->IsDisabled()) |
| return it; |
| } |
| |
| return controllers_.end(); |
| } |
| |
| bool ScreenManager::HandleMirrorMode( |
| HardwareDisplayControllers::iterator original, |
| HardwareDisplayControllers::iterator mirror, |
| const scoped_refptr<DrmDevice>& drm, |
| uint32_t crtc, |
| uint32_t connector, |
| const drmModeModeInfo& mode) { |
| gfx::Point last_origin = (*original)->origin(); |
| // There should only be one CRTC in this controller. |
| drmModeModeInfo last_mode = (*original)->crtc_controllers()[0]->mode(); |
| |
| // Modeset the CRTC with its mode in the original controller so that only this |
| // CRTC is affected by the mode. Otherwise it could apply a mode with the same |
| // resolution and refresh rate but with different timings to the other CRTC. |
| // TODO(dnicoara): This is hacky, instead the DrmDisplay and CrtcController |
| // should be merged and picking the mode should be done properly within |
| // HardwareDisplayController. |
| if (ModesetController(original->get(), (*mirror)->origin(), mode)) { |
| (*mirror)->AddCrtc((*original)->RemoveCrtc(drm, crtc)); |
| controllers_.erase(original); |
| return true; |
| } |
| |
| LOG(ERROR) << "Failed to switch to mirror mode"; |
| |
| // When things go wrong revert back to the previous configuration since |
| // it is expected that the configuration would not have changed if |
| // things fail. |
| ModesetController(original->get(), last_origin, last_mode); |
| return false; |
| } |
| |
| void ScreenManager::UpdateControllerToWindowMapping() { |
| std::map<DrmWindow*, HardwareDisplayController*> window_to_controller_map; |
| // First create a unique mapping between a window and a controller. Note, a |
| // controller may be associated with at most 1 window. |
| for (const auto& controller : controllers_) { |
| if (controller->IsDisabled()) |
| continue; |
| |
| DrmWindow* window = FindWindowAt( |
| gfx::Rect(controller->origin(), controller->GetModeSize())); |
| if (!window) |
| continue; |
| |
| window_to_controller_map[window] = controller.get(); |
| } |
| |
| // Apply the new mapping to all windows. |
| for (auto& pair : window_map_) { |
| auto it = window_to_controller_map.find(pair.second.get()); |
| HardwareDisplayController* controller = nullptr; |
| if (it != window_to_controller_map.end()) |
| controller = it->second; |
| |
| bool should_enable = |
| controller && pair.second->GetController() != controller; |
| pair.second->SetController(controller); |
| |
| // If we're moving windows between controllers modeset the controller |
| // otherwise the controller may be waiting for a page flip while the window |
| // tries to schedule another buffer. |
| if (should_enable) { |
| EnableController(controller); |
| } |
| } |
| } |
| |
| DrmOverlayPlane ScreenManager::GetModesetBuffer( |
| HardwareDisplayController* controller, |
| const gfx::Rect& bounds) { |
| DrmWindow* window = FindWindowAt(bounds); |
| |
| gfx::BufferFormat format = display::DisplaySnapshot::PrimaryFormat(); |
| uint32_t fourcc_format = ui::GetFourCCFormatForOpaqueFramebuffer(format); |
| const auto& modifiers = |
| controller->GetFormatModifiersForModesetting(fourcc_format); |
| if (window) { |
| const DrmOverlayPlane* primary = window->GetLastModesetBuffer(); |
| const DrmDevice* drm = controller->GetDrmDevice().get(); |
| if (primary && primary->buffer->GetSize() == bounds.size() && |
| primary->buffer->GetGbmDeviceLinux() == drm->AsGbmDeviceLinux()) { |
| // If the controller doesn't advertise modifiers, wont have a |
| // modifier either and we can reuse the buffer. Otherwise, check |
| // to see if the controller supports the buffers format |
| // modifier. |
| if (modifiers.empty()) |
| return primary->Clone(); |
| for (const uint64_t modifier : modifiers) { |
| if (modifier == primary->buffer->GetFormatModifier()) |
| return primary->Clone(); |
| } |
| } |
| } |
| |
| scoped_refptr<DrmDevice> drm = controller->GetDrmDevice(); |
| scoped_refptr<ScanoutBuffer> buffer = |
| buffer_generator_->Create(drm, fourcc_format, modifiers, bounds.size()); |
| if (!buffer) { |
| LOG(ERROR) << "Failed to create scanout buffer"; |
| return DrmOverlayPlane(nullptr, 0, gfx::OVERLAY_TRANSFORM_INVALID, |
| gfx::Rect(), gfx::RectF(), /* enable_blend */ true, |
| /* gpu_fence */ nullptr); |
| } |
| |
| FillModesetBuffer(drm, controller, buffer.get()); |
| return DrmOverlayPlane(buffer, nullptr); |
| } |
| |
| bool ScreenManager::EnableController(HardwareDisplayController* controller) { |
| DCHECK(!controller->crtc_controllers().empty()); |
| gfx::Rect rect(controller->origin(), controller->GetModeSize()); |
| DrmOverlayPlane plane = GetModesetBuffer(controller, rect); |
| if (!plane.buffer || !controller->Enable(plane)) { |
| LOG(ERROR) << "Failed to enable controller"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ScreenManager::ModesetController(HardwareDisplayController* controller, |
| const gfx::Point& origin, |
| const drmModeModeInfo& mode) { |
| DCHECK(!controller->crtc_controllers().empty()); |
| gfx::Rect rect(origin, gfx::Size(mode.hdisplay, mode.vdisplay)); |
| controller->set_origin(origin); |
| |
| DrmOverlayPlane plane = GetModesetBuffer(controller, rect); |
| if (!plane.buffer || !controller->Modeset(plane, mode)) { |
| LOG(ERROR) << "Failed to modeset controller"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| DrmWindow* ScreenManager::FindWindowAt(const gfx::Rect& bounds) const { |
| for (auto& pair : window_map_) { |
| if (pair.second->bounds() == bounds) |
| return pair.second.get(); |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace ui |