| // Copyright 2015 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/drm_thread.h" |
| |
| #include <gbm.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/time/time.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "base/trace_event/trace_event.h" |
| #include "ui/gfx/linux/drm_util_linux.h" |
| // For standard Linux/system libgbm |
| #include "ui/gfx/linux/gbm_defines.h" |
| #include "ui/gfx/linux/gbm_device.h" |
| #include "ui/gfx/linux/gbm_util.h" |
| #include "ui/gfx/presentation_feedback.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_device_generator.h" |
| #include "ui/ozone/platform/drm/gpu/drm_device_manager.h" |
| #include "ui/ozone/platform/drm/gpu/drm_gpu_display_manager.h" |
| #include "ui/ozone/platform/drm/gpu/drm_window.h" |
| #include "ui/ozone/platform/drm/gpu/gbm_pixmap.h" |
| #include "ui/ozone/platform/drm/gpu/screen_manager.h" |
| |
| namespace ui { |
| |
| namespace { |
| |
| void CreateBufferWithGbmFlags(const scoped_refptr<DrmDevice>& drm, |
| uint32_t fourcc_format, |
| const gfx::Size& size, |
| const gfx::Size& framebuffer_size, |
| uint32_t flags, |
| const std::vector<uint64_t>& modifiers, |
| std::unique_ptr<GbmBuffer>* out_buffer, |
| scoped_refptr<DrmFramebuffer>* out_framebuffer) { |
| std::unique_ptr<GbmBuffer> buffer = |
| drm->gbm_device()->CreateBufferWithModifiers(fourcc_format, size, flags, |
| modifiers); |
| if (!buffer) |
| return; |
| |
| scoped_refptr<DrmFramebuffer> framebuffer; |
| if (flags & GBM_BO_USE_SCANOUT) { |
| framebuffer = DrmFramebuffer::AddFramebuffer(drm, buffer.get(), |
| framebuffer_size, modifiers); |
| if (!framebuffer) |
| return; |
| } |
| |
| *out_buffer = std::move(buffer); |
| *out_framebuffer = std::move(framebuffer); |
| } |
| |
| } // namespace |
| |
| DrmThread::TaskInfo::TaskInfo(base::OnceClosure task, base::WaitableEvent* done) |
| : task(std::move(task)), done(done) {} |
| |
| DrmThread::TaskInfo::TaskInfo(TaskInfo&& other) = default; |
| |
| DrmThread::TaskInfo::~TaskInfo() = default; |
| |
| DrmThread::DrmThread() : base::Thread("DrmThread") {} |
| |
| DrmThread::~DrmThread() { |
| Stop(); |
| } |
| |
| void DrmThread::Start(base::OnceClosure receiver_completer, |
| std::unique_ptr<DrmDeviceGenerator> device_generator) { |
| complete_early_receiver_requests_ = std::move(receiver_completer); |
| device_generator_ = std::move(device_generator); |
| |
| base::Thread::Options thread_options; |
| thread_options.message_pump_type = base::MessagePumpType::IO; |
| thread_options.thread_type = base::ThreadType::kDisplayCritical; |
| |
| if (!StartWithOptions(std::move(thread_options))) |
| LOG(FATAL) << "Failed to create DRM thread"; |
| } |
| |
| void DrmThread::RunTaskAfterDeviceReady(base::OnceClosure task, |
| base::WaitableEvent* done) { |
| if (!device_manager_->GetDrmDevices().empty()) { |
| std::move(task).Run(); |
| if (done) |
| done->Signal(); |
| return; |
| } |
| pending_tasks_.emplace_back(std::move(task), done); |
| } |
| |
| void DrmThread::Init() { |
| TRACE_EVENT0("drm", "DrmThread::Init"); |
| device_manager_ = |
| std::make_unique<DrmDeviceManager>(std::move(device_generator_)); |
| screen_manager_ = std::make_unique<ScreenManager>(); |
| display_manager_ = std::make_unique<DrmGpuDisplayManager>( |
| screen_manager_.get(), device_manager_.get()); |
| |
| DCHECK(task_runner()) |
| << "DrmThread::Init -- thread doesn't have a task_runner"; |
| |
| // DRM thread is running now so can safely handle receiver requests. So drain |
| // the queue of as-yet unhandled receiver requests if there are any. |
| std::move(complete_early_receiver_requests_).Run(); |
| } |
| |
| void DrmThread::CleanUp() { |
| TRACE_EVENT0("drm", "DrmThread::CleanUp"); |
| display_manager_.reset(); |
| screen_manager_.reset(); |
| device_manager_.reset(); |
| } |
| |
| void DrmThread::CreateBuffer(gfx::AcceleratedWidget widget, |
| const gfx::Size& size, |
| const gfx::Size& framebuffer_size, |
| gfx::BufferFormat format, |
| gfx::BufferUsage usage, |
| uint32_t client_flags, |
| std::unique_ptr<GbmBuffer>* buffer, |
| scoped_refptr<DrmFramebuffer>* framebuffer) { |
| TRACE_EVENT0("drm", "DrmThread::CreateBuffer"); |
| scoped_refptr<ui::DrmDevice> drm = device_manager_->GetDrmDevice(widget); |
| CHECK(drm) << "No devices available for buffer allocation."; |
| |
| DrmWindow* window = screen_manager_->GetWindow(widget); |
| uint32_t flags = BufferUsageToGbmFlags(usage); |
| uint32_t fourcc_format = GetFourCCFormatFromBufferFormat(format); |
| |
| // Some modifiers are incompatible with some gbm_bo_flags. If we give |
| // modifiers to the GBM allocator, then GBM ignores the flags, and therefore |
| // may choose a modifier that's incompatible with the intended usage. |
| // Therefore, leave the modifier list empty for problematic flags. |
| // |
| // TODO(chadversary): Define GBM api that reports the modifiers compatible |
| // with a given set of use flags. |
| // |
| // TODO(hoegsberg): We shouldn't really get here without a window, |
| // but it happens during init. Need to figure out why. |
| std::vector<uint64_t> modifiers; |
| if (window && window->GetController() && !(flags & GBM_BO_USE_LINEAR) && |
| !(flags & GBM_BO_USE_HW_VIDEO_DECODER) && |
| !(client_flags & GbmPixmap::kFlagNoModifiers)) { |
| modifiers = window->GetController()->GetSupportedModifiers(fourcc_format); |
| } |
| |
| CreateBufferWithGbmFlags(drm, fourcc_format, size, framebuffer_size, flags, |
| modifiers, buffer, framebuffer); |
| |
| // NOTE: BufferUsage::SCANOUT is used to create buffers that will be |
| // explicitly set via kms on a CRTC (e.g: BufferQueue buffers), therefore |
| // allocation should fail if it's not possible to allocate a BO_USE_SCANOUT |
| // buffer in that case. |
| if (!*buffer && usage != gfx::BufferUsage::SCANOUT && |
| usage != gfx::BufferUsage::PROTECTED_SCANOUT_VDA_WRITE && |
| usage != gfx::BufferUsage::SCANOUT_FRONT_RENDERING) { |
| flags &= ~GBM_BO_USE_SCANOUT; |
| CreateBufferWithGbmFlags(drm, fourcc_format, size, framebuffer_size, flags, |
| modifiers, buffer, framebuffer); |
| } |
| } |
| |
| void DrmThread::CreateBufferAsync(gfx::AcceleratedWidget widget, |
| const gfx::Size& size, |
| gfx::BufferFormat format, |
| gfx::BufferUsage usage, |
| uint32_t client_flags, |
| CreateBufferAsyncCallback callback) { |
| TRACE_EVENT0("drm", "DrmThread::CreateBufferAsync"); |
| std::unique_ptr<GbmBuffer> buffer; |
| scoped_refptr<DrmFramebuffer> framebuffer; |
| CreateBuffer(widget, size, /*framebuffer_size=*/size, format, usage, |
| client_flags, &buffer, &framebuffer); |
| std::move(callback).Run(std::move(buffer), std::move(framebuffer)); |
| } |
| |
| void DrmThread::CreateBufferFromHandle( |
| gfx::AcceleratedWidget widget, |
| const gfx::Size& size, |
| gfx::BufferFormat format, |
| gfx::NativePixmapHandle handle, |
| std::unique_ptr<GbmBuffer>* out_buffer, |
| scoped_refptr<DrmFramebuffer>* out_framebuffer) { |
| TRACE_EVENT0("drm", "DrmThread::CreateBufferFromHandle"); |
| scoped_refptr<ui::DrmDevice> drm = device_manager_->GetDrmDevice(widget); |
| DCHECK(drm); |
| |
| std::unique_ptr<GbmBuffer> buffer = drm->gbm_device()->CreateBufferFromHandle( |
| GetFourCCFormatFromBufferFormat(format), size, std::move(handle)); |
| if (!buffer) |
| return; |
| |
| scoped_refptr<DrmFramebuffer> framebuffer; |
| if (buffer->GetFlags() & GBM_BO_USE_SCANOUT) { |
| // NB: This is not required to succeed; framebuffers are added for |
| // imported buffers on a best effort basis. |
| framebuffer = |
| DrmFramebuffer::AddFramebuffer(drm, buffer.get(), buffer->GetSize()); |
| } |
| |
| *out_buffer = std::move(buffer); |
| *out_framebuffer = std::move(framebuffer); |
| } |
| |
| void DrmThread::SetDisplaysConfiguredCallback(base::RepeatingClosure callback) { |
| display_manager_->SetDisplaysConfiguredCallback(std::move(callback)); |
| } |
| |
| void DrmThread::SchedulePageFlip( |
| gfx::AcceleratedWidget widget, |
| std::vector<DrmOverlayPlane> planes, |
| SwapCompletionOnceCallback submission_callback, |
| PresentationOnceCallback presentation_callback) { |
| TRACE_EVENT0("drm", "DrmThread::SchedulePageFlip"); |
| scoped_refptr<ui::DrmDevice> drm_device = |
| device_manager_->GetDrmDevice(widget); |
| |
| drm_device->plane_manager()->RequestPlanesReadyCallback( |
| std::move(planes), base::BindOnce(&DrmThread::OnPlanesReadyForPageFlip, |
| weak_ptr_factory_.GetWeakPtr(), widget, |
| std::move(submission_callback), |
| std::move(presentation_callback))); |
| } |
| |
| void DrmThread::OnPlanesReadyForPageFlip( |
| gfx::AcceleratedWidget widget, |
| SwapCompletionOnceCallback submission_callback, |
| PresentationOnceCallback presentation_callback, |
| std::vector<DrmOverlayPlane> planes) { |
| TRACE_EVENT0("drm", "DrmThread::OnPlanesReadyForPageFlip"); |
| DrmWindow* window = screen_manager_->GetWindow(widget); |
| if (window) { |
| window->SchedulePageFlip(std::move(planes), std::move(submission_callback), |
| std::move(presentation_callback)); |
| } else { |
| std::move(submission_callback) |
| .Run(gfx::SwapResult::SWAP_ACK, |
| /*release_fence=*/gfx::GpuFenceHandle()); |
| std::move(presentation_callback).Run(gfx::PresentationFeedback::Failure()); |
| } |
| } |
| |
| void DrmThread::IsDeviceAtomic(gfx::AcceleratedWidget widget, bool* is_atomic) { |
| TRACE_EVENT0("drm", "DrmThread::IsDeviceAtomic"); |
| scoped_refptr<ui::DrmDevice> drm_device = |
| device_manager_->GetDrmDevice(widget); |
| |
| *is_atomic = drm_device && drm_device->is_atomic(); |
| } |
| |
| void DrmThread::SetDrmModifiersFilter( |
| std::unique_ptr<DrmModifiersFilter> filter) { |
| screen_manager_->SetDrmModifiersFilter(std::move(filter)); |
| } |
| |
| void DrmThread::CreateWindow(gfx::AcceleratedWidget widget, |
| const gfx::Rect& initial_bounds) { |
| TRACE_EVENT0("drm", "DrmThread::CreateWindow"); |
| DCHECK_GT(widget, last_created_window_); |
| last_created_window_ = widget; |
| |
| std::unique_ptr<DrmWindow> window( |
| new DrmWindow(widget, device_manager_.get(), screen_manager_.get())); |
| window->Initialize(); |
| screen_manager_->AddWindow(widget, std::move(window)); |
| screen_manager_->GetWindow(widget)->SetBounds(initial_bounds); |
| |
| // There might be tasks that were waiting for |widget| to become available. |
| ProcessPendingTasks(); |
| } |
| |
| void DrmThread::DestroyWindow(gfx::AcceleratedWidget widget) { |
| TRACE_EVENT0("drm", "DrmThread::DestroyWindow"); |
| std::unique_ptr<DrmWindow> window = screen_manager_->RemoveWindow(widget); |
| window->Shutdown(); |
| } |
| |
| void DrmThread::SetWindowBounds(gfx::AcceleratedWidget widget, |
| const gfx::Rect& bounds) { |
| TRACE_EVENT0("drm", "DrmThread::SetWindowBounds"); |
| screen_manager_->GetWindow(widget)->SetBounds(bounds); |
| } |
| |
| void DrmThread::SetCursor(gfx::AcceleratedWidget widget, |
| const std::vector<SkBitmap>& bitmaps, |
| const absl::optional<gfx::Point>& location, |
| base::TimeDelta frame_delay) { |
| TRACE_EVENT0("drm", "DrmThread::SetCursor"); |
| screen_manager_->GetWindow(widget)->SetCursor(bitmaps, location, frame_delay); |
| } |
| |
| void DrmThread::MoveCursor(gfx::AcceleratedWidget widget, |
| const gfx::Point& location) { |
| TRACE_EVENT0("drm", "DrmThread::MoveCursor"); |
| screen_manager_->GetWindow(widget)->MoveCursor(location); |
| } |
| |
| void DrmThread::CheckOverlayCapabilities( |
| gfx::AcceleratedWidget widget, |
| const OverlaySurfaceCandidateList& overlays, |
| OverlayCapabilitiesCallback callback) { |
| TRACE_EVENT0("drm,hwoverlays", "DrmThread::CheckOverlayCapabilities"); |
| |
| std::vector<OverlayStatus> result; |
| CheckOverlayCapabilitiesSync(widget, overlays, &result); |
| std::move(callback).Run(widget, overlays, std::move(result)); |
| } |
| |
| void DrmThread::CheckOverlayCapabilitiesSync( |
| gfx::AcceleratedWidget widget, |
| const OverlaySurfaceCandidateList& overlays, |
| std::vector<OverlayStatus>* result) { |
| TRACE_EVENT0("drm,hwoverlays", "DrmThread::CheckOverlayCapabilitiesSync"); |
| base::ElapsedTimer timer; |
| |
| DrmWindow* window = screen_manager_->GetWindow(widget); |
| if (!window) { |
| result->clear(); |
| result->insert(result->end(), overlays.size(), OVERLAY_STATUS_NOT); |
| return; |
| } |
| *result = window->TestPageFlip(overlays); |
| |
| base::TimeDelta time = timer.Elapsed(); |
| static constexpr base::TimeDelta kMinTime = base::Microseconds(1); |
| static constexpr base::TimeDelta kMaxTime = base::Milliseconds(10); |
| static constexpr int kTimeBuckets = 50; |
| UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( |
| "Compositing.Display.DrmThread.CheckOverlayCapabilitiesSyncUs", time, |
| kMinTime, kMaxTime, kTimeBuckets); |
| } |
| |
| void DrmThread::GetHardwareCapabilities( |
| gfx::AcceleratedWidget widget, |
| HardwareCapabilitiesCallback receive_callback) { |
| TRACE_EVENT0("drm,hwoverlays", "DrmThread::GetHardwareCapabilities"); |
| DCHECK(screen_manager_->GetWindow(widget)); |
| DCHECK(device_manager_->GetDrmDevice(widget)); |
| HardwareDisplayController* hdc = |
| screen_manager_->GetWindow(widget)->GetController(); |
| HardwareDisplayPlaneManager* plane_manager = |
| device_manager_->GetDrmDevice(widget)->plane_manager(); |
| |
| if (!hdc || !plane_manager) { |
| HardwareCapabilities hardware_capabilities{.is_valid = false}; |
| std::move(receive_callback).Run(hardware_capabilities); |
| return; |
| } |
| |
| const auto& crtc_controllers = hdc->crtc_controllers(); |
| // We almost always expect only one CRTC. Multiple CRTCs for one widget was |
| // the old way mirror mode was supported. |
| if (crtc_controllers.size() == 1) { |
| std::move(receive_callback) |
| .Run(plane_manager->GetHardwareCapabilities( |
| crtc_controllers[0]->crtc())); |
| } else { |
| // If there are multiple CRTCs for this widget we shouldn't rely on overlays |
| // working. |
| HardwareCapabilities hardware_capabilities{.is_valid = false}; |
| std::move(receive_callback).Run(hardware_capabilities); |
| } |
| } |
| |
| void DrmThread::GetDeviceCursor( |
| mojo::PendingAssociatedReceiver<ozone::mojom::DeviceCursor> receiver) { |
| cursor_receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void DrmThread::RefreshNativeDisplays( |
| base::OnceCallback<void(MovableDisplaySnapshots)> callback) { |
| TRACE_EVENT0("drm", "DrmThread::RefreshNativeDisplays"); |
| std::move(callback).Run(display_manager_->GetDisplays()); |
| } |
| |
| void DrmThread::ConfigureNativeDisplays( |
| const std::vector<display::DisplayConfigurationParams>& config_requests, |
| uint32_t modeset_flag, |
| base::OnceCallback<void(bool)> callback) { |
| TRACE_EVENT0("drm", "DrmThread::ConfigureNativeDisplays"); |
| |
| bool config_success = |
| display_manager_->ConfigureDisplays(config_requests, modeset_flag); |
| std::move(callback).Run(config_success); |
| } |
| |
| void DrmThread::TakeDisplayControl(base::OnceCallback<void(bool)> callback) { |
| TRACE_EVENT0("drm", "DrmThread::TakeDisplayControl"); |
| std::move(callback).Run(display_manager_->TakeDisplayControl()); |
| } |
| |
| void DrmThread::RelinquishDisplayControl( |
| base::OnceCallback<void(bool)> callback) { |
| TRACE_EVENT0("drm", "DrmThread::RelinquishDisplayControl"); |
| display_manager_->RelinquishDisplayControl(); |
| std::move(callback).Run(true); |
| } |
| |
| void DrmThread::ShouldDisplayEventTriggerConfiguration( |
| const EventPropertyMap& event_props, |
| base::OnceCallback<void(bool)> callback) { |
| TRACE_EVENT0("drm", "DrmThread::ShouldDisplayEventTriggerConfiguration"); |
| const bool should_trigger = |
| display_manager_->ShouldDisplayEventTriggerConfiguration(event_props); |
| |
| std::move(callback).Run(should_trigger); |
| } |
| |
| void DrmThread::AddGraphicsDevice(const base::FilePath& path, |
| mojo::PlatformHandle fd_mojo_handle) { |
| TRACE_EVENT0("drm", "DrmThread::AddGraphicsDevice"); |
| device_manager_->AddDrmDevice(path, fd_mojo_handle.TakeFD()); |
| |
| // There might be tasks that were blocked on a DrmDevice becoming available. |
| ProcessPendingTasks(); |
| } |
| |
| void DrmThread::RemoveGraphicsDevice(const base::FilePath& path) { |
| TRACE_EVENT0("drm", "DrmThread::RemoveGraphicsDevice"); |
| device_manager_->RemoveDrmDevice(path); |
| } |
| |
| void DrmThread::SetHdcpKeyProp(int64_t display_id, |
| const std::string& key, |
| SetHdcpKeyPropCallback callback) { |
| TRACE_EVENT0("drm", "DrmThread::SetHdcpKeyProp"); |
| bool is_prop_set = display_manager_->SetHdcpKeyProp(display_id, key); |
| std::move(callback).Run(display_id, is_prop_set); |
| } |
| |
| void DrmThread::GetHDCPState( |
| int64_t display_id, |
| base::OnceCallback<void(int64_t, |
| bool, |
| display::HDCPState, |
| display::ContentProtectionMethod)> callback) { |
| TRACE_EVENT0("drm", "DrmThread::GetHDCPState"); |
| display::HDCPState state = display::HDCP_STATE_UNDESIRED; |
| display::ContentProtectionMethod protection_method = |
| display::CONTENT_PROTECTION_METHOD_NONE; |
| bool success = |
| display_manager_->GetHDCPState(display_id, &state, &protection_method); |
| std::move(callback).Run(display_id, success, state, protection_method); |
| } |
| |
| void DrmThread::SetHDCPState(int64_t display_id, |
| display::HDCPState state, |
| display::ContentProtectionMethod protection_method, |
| base::OnceCallback<void(int64_t, bool)> callback) { |
| TRACE_EVENT0("drm", "DrmThread::SetHDCPState"); |
| std::move(callback).Run( |
| display_id, |
| display_manager_->SetHDCPState(display_id, state, protection_method)); |
| } |
| |
| void DrmThread::SetColorMatrix(int64_t display_id, |
| const std::vector<float>& color_matrix) { |
| TRACE_EVENT0("drm", "DrmThread::SetColorMatrix"); |
| display_manager_->SetColorMatrix(display_id, color_matrix); |
| } |
| |
| void DrmThread::SetGammaCorrection(int64_t display_id, |
| const display::GammaCurve& degamma, |
| const display::GammaCurve& gamma) { |
| TRACE_EVENT0("drm", "DrmThread::SetGammaCorrection"); |
| display_manager_->SetGammaCorrection(display_id, degamma, gamma); |
| } |
| |
| void DrmThread::SetPrivacyScreen(int64_t display_id, |
| bool enabled, |
| base::OnceCallback<void(bool)> callback) { |
| bool success = display_manager_->SetPrivacyScreen(display_id, enabled); |
| std::move(callback).Run(success); |
| } |
| |
| void DrmThread::AddDrmDeviceReceiver( |
| mojo::PendingReceiver<ozone::mojom::DrmDevice> receiver) { |
| TRACE_EVENT0("drm", "DrmThread::AddDrmDeviceReceiver"); |
| drm_receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void DrmThread::ProcessPendingTasks() { |
| DCHECK(!device_manager_->GetDrmDevices().empty()); |
| |
| for (auto& task_info : pending_tasks_) { |
| std::move(task_info.task).Run(); |
| if (task_info.done) |
| task_info.done->Signal(); |
| } |
| |
| pending_tasks_.clear(); |
| } |
| |
| void DrmThread::SetColorSpace(gfx::AcceleratedWidget widget, |
| const gfx::ColorSpace& color_space) { |
| DCHECK(screen_manager_->GetWindow(widget)); |
| HardwareDisplayController* controller = |
| screen_manager_->GetWindow(widget)->GetController(); |
| if (!controller) |
| return; |
| |
| const auto& crtc_controllers = controller->crtc_controllers(); |
| for (const auto& crtc_controller : crtc_controllers) |
| display_manager_->SetColorSpace(crtc_controller->crtc(), color_space); |
| } |
| |
| } // namespace ui |