1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/ozone/platform/drm/gpu/hardware_display_plane_manager_atomic.h"
6 
7 #include <sync/sync.h>
8 #include <xf86drm.h>
9 #include <xf86drmMode.h>
10 #include <memory>
11 #include <utility>
12 
13 #include "base/bind.h"
14 #include "base/containers/flat_set.h"
15 #include "base/files/platform_file.h"
16 #include "base/logging.h"
17 #include "base/stl_util.h"
18 #include "base/threading/sequenced_task_runner_handle.h"
19 #include "ui/gfx/gpu_fence.h"
20 #include "ui/gfx/gpu_fence_handle.h"
21 #include "ui/ozone/platform/drm/common/drm_util.h"
22 #include "ui/ozone/platform/drm/gpu/crtc_controller.h"
23 #include "ui/ozone/platform/drm/gpu/drm_device.h"
24 #include "ui/ozone/platform/drm/gpu/drm_framebuffer.h"
25 #include "ui/ozone/platform/drm/gpu/drm_gpu_util.h"
26 #include "ui/ozone/platform/drm/gpu/hardware_display_plane_atomic.h"
27 #include "ui/ozone/platform/drm/gpu/page_flip_request.h"
28 
29 namespace ui {
30 
31 namespace {
32 
CreateMergedGpuFenceFromFDs(std::vector<base::ScopedFD> fence_fds)33 std::unique_ptr<gfx::GpuFence> CreateMergedGpuFenceFromFDs(
34     std::vector<base::ScopedFD> fence_fds) {
35   base::ScopedFD merged_fd;
36 
37   for (auto& fd : fence_fds) {
38     if (merged_fd.is_valid()) {
39       merged_fd.reset(sync_merge("", merged_fd.get(), fd.get()));
40       DCHECK(merged_fd.is_valid());
41     } else {
42       merged_fd = std::move(fd);
43     }
44   }
45 
46   if (merged_fd.is_valid()) {
47     gfx::GpuFenceHandle handle;
48     handle.owned_fd = std::move(merged_fd);
49     return std::make_unique<gfx::GpuFence>(std::move(handle));
50   }
51 
52   return nullptr;
53 }
54 
GetCrtcIdsOfPlanes(const HardwareDisplayPlaneList & plane_list)55 std::vector<uint32_t> GetCrtcIdsOfPlanes(
56     const HardwareDisplayPlaneList& plane_list) {
57   std::vector<uint32_t> crtcs;
58   for (HardwareDisplayPlane* plane : plane_list.plane_list) {
59     HardwareDisplayPlaneAtomic* atomic_plane =
60         static_cast<HardwareDisplayPlaneAtomic*>(plane);
61     if (crtcs.empty() || crtcs.back() != atomic_plane->AssignedCrtcId())
62       crtcs.push_back(atomic_plane->AssignedCrtcId());
63   }
64   return crtcs;
65 }
66 
67 }  // namespace
68 
HardwareDisplayPlaneManagerAtomic(DrmDevice * drm)69 HardwareDisplayPlaneManagerAtomic::HardwareDisplayPlaneManagerAtomic(
70     DrmDevice* drm)
71     : HardwareDisplayPlaneManager(drm) {}
72 
73 HardwareDisplayPlaneManagerAtomic::~HardwareDisplayPlaneManagerAtomic() =
74     default;
75 
SetCrtcProps(drmModeAtomicReq * atomic_request,uint32_t crtc_id,bool set_active,uint32_t mode_id)76 bool HardwareDisplayPlaneManagerAtomic::SetCrtcProps(
77     drmModeAtomicReq* atomic_request,
78     uint32_t crtc_id,
79     bool set_active,
80     uint32_t mode_id) {
81   // Only making a copy here to retrieve the the props IDs. The state will be
82   // updated only after a successful modeset.
83   CrtcProperties modeset_props = GetCrtcStateForCrtcId(crtc_id).properties;
84   modeset_props.active.value = static_cast<uint64_t>(set_active);
85   modeset_props.mode_id.value = mode_id;
86 
87   bool status =
88       AddPropertyIfValid(atomic_request, crtc_id, modeset_props.active);
89   status &= AddPropertyIfValid(atomic_request, crtc_id, modeset_props.mode_id);
90   return status;
91 }
92 
SetConnectorProps(drmModeAtomicReq * atomic_request,uint32_t connector_id,uint32_t crtc_id)93 bool HardwareDisplayPlaneManagerAtomic::SetConnectorProps(
94     drmModeAtomicReq* atomic_request,
95     uint32_t connector_id,
96     uint32_t crtc_id) {
97   int connector_index = LookupConnectorIndex(connector_id);
98   DCHECK_GE(connector_index, 0);
99   // Only making a copy here to retrieve the the props IDs. The state will be
100   // updated only after a successful modeset.
101   ConnectorProperties connector_props = connectors_props_[connector_index];
102   connector_props.crtc_id.value = crtc_id;
103 
104   return AddPropertyIfValid(atomic_request, connector_id,
105                             connector_props.crtc_id);
106 }
107 
Commit(CommitRequest commit_request,uint32_t flags)108 bool HardwareDisplayPlaneManagerAtomic::Commit(CommitRequest commit_request,
109                                                uint32_t flags) {
110   bool is_testing = flags & DRM_MODE_ATOMIC_TEST_ONLY;
111   bool status = true;
112 
113   std::vector<ScopedDrmPropertyBlob> scoped_blobs;
114 
115   base::flat_set<HardwareDisplayPlaneList*> enable_planes_lists;
116   base::flat_set<HardwareDisplayPlaneList*> all_planes_lists;
117 
118   ScopedDrmAtomicReqPtr atomic_request(drmModeAtomicAlloc());
119 
120   for (const auto& crtc_request : commit_request) {
121     if (crtc_request.plane_list())
122       all_planes_lists.insert(crtc_request.plane_list());
123 
124     uint32_t mode_id = 0;
125     if (crtc_request.should_enable()) {
126       auto mode_blob = drm_->CreatePropertyBlob(&crtc_request.mode(),
127                                                 sizeof(crtc_request.mode()));
128       status &= (mode_blob != nullptr);
129       if (mode_blob) {
130         scoped_blobs.push_back(std::move(mode_blob));
131         mode_id = scoped_blobs.back()->id();
132       }
133     }
134 
135     uint32_t crtc_id = crtc_request.crtc_id();
136 
137     status &= SetCrtcProps(atomic_request.get(), crtc_id,
138                            crtc_request.should_enable(), mode_id);
139     status &=
140         SetConnectorProps(atomic_request.get(), crtc_request.connector_id(),
141                           crtc_request.should_enable() * crtc_id);
142 
143     if (crtc_request.should_enable()) {
144       DCHECK(crtc_request.plane_list());
145       status &= AssignOverlayPlanes(crtc_request.plane_list(),
146                                     crtc_request.overlays(), crtc_id);
147       enable_planes_lists.insert(crtc_request.plane_list());
148     }
149   }
150 
151   // TODO(markyacoub): Ideally this doesn't need to be a separate step. It
152   // should all be handled in Set{Crtc,Connector,Plane}Props() modulo some state
153   // tracking changes that should be done post commit. Break it apart when both
154   // Commit() are consolidated.
155   for (HardwareDisplayPlaneList* list : enable_planes_lists) {
156     SetAtomicPropsForCommit(atomic_request.get(), list,
157                             GetCrtcIdsOfPlanes(*list), is_testing);
158   }
159 
160   // TODO(markyacoub): failed |status|'s should be made as DCHECKs. The only
161   // reason some of these would be failing is OOM. If we OOM-ed there's no point
162   // in trying to recover.
163   if (!status || !drm_->CommitProperties(atomic_request.get(), flags,
164                                          commit_request.size(), nullptr)) {
165     if (is_testing)
166       VPLOG(2) << "Modeset Test is rejected.";
167     else
168       PLOG(ERROR) << "Failed to commit properties for modeset.";
169 
170     for (HardwareDisplayPlaneList* list : all_planes_lists)
171       ResetCurrentPlaneList(list);
172 
173     return false;
174   }
175 
176   if (!is_testing)
177     UpdateCrtcAndPlaneStatesAfterModeset(commit_request);
178 
179   for (HardwareDisplayPlaneList* list : enable_planes_lists)
180     list->plane_list.clear();
181 
182   return true;
183 }
184 
SetAtomicPropsForCommit(drmModeAtomicReq * atomic_request,HardwareDisplayPlaneList * plane_list,const std::vector<uint32_t> & crtcs,bool test_only)185 void HardwareDisplayPlaneManagerAtomic::SetAtomicPropsForCommit(
186     drmModeAtomicReq* atomic_request,
187     HardwareDisplayPlaneList* plane_list,
188     const std::vector<uint32_t>& crtcs,
189     bool test_only) {
190   for (HardwareDisplayPlane* plane : plane_list->plane_list) {
191     HardwareDisplayPlaneAtomic* atomic_plane =
192         static_cast<HardwareDisplayPlaneAtomic*>(plane);
193     atomic_plane->SetPlaneProps(atomic_request);
194   }
195 
196   for (HardwareDisplayPlane* plane : plane_list->old_plane_list) {
197     if (!base::Contains(plane_list->plane_list, plane)) {
198       // |plane| is shared state between |old_plane_list| and |plane_list|.
199       // When we call BeginFrame(), we reset in_use since we need to be able to
200       // allocate the planes as needed. The current frame might not need to use
201       // |plane|, thus |plane->in_use()| would be false even though the previous
202       // frame used it. It's existence in |old_plane_list| is sufficient to
203       // signal that |plane| was in use previously.
204       plane->set_in_use(false);
205       HardwareDisplayPlaneAtomic* atomic_plane =
206           static_cast<HardwareDisplayPlaneAtomic*>(plane);
207       atomic_plane->AssignPlaneProps(0, 0, gfx::Rect(), gfx::Rect(),
208                                      gfx::OVERLAY_TRANSFORM_NONE,
209                                      base::kInvalidPlatformFile);
210       atomic_plane->SetPlaneProps(atomic_request);
211     }
212   }
213 
214   for (uint32_t crtc : crtcs) {
215     int idx = LookupCrtcIndex(crtc);
216 
217 #if defined(COMMIT_PROPERTIES_ON_PAGE_FLIP)
218     // Apply all CRTC properties in the page-flip so we don't block the
219     // swap chain for a vsync.
220     // TODO(dnicoara): See if we can apply these properties async using
221     // DRM_MODE_ATOMIC_ASYNC_UPDATE flag when committing.
222     AddPropertyIfValid(atomic_request, crtc,
223                        crtc_state_[idx].properties.degamma_lut);
224     AddPropertyIfValid(atomic_request, crtc,
225                        crtc_state_[idx].properties.gamma_lut);
226     AddPropertyIfValid(atomic_request, crtc, crtc_state_[idx].properties.ctm);
227 #endif
228 
229     AddPropertyIfValid(atomic_request, crtc,
230                        crtc_state_[idx].properties.background_color);
231   }
232 
233   if (test_only) {
234     for (auto* plane : plane_list->plane_list) {
235       plane->set_in_use(false);
236     }
237     for (auto* plane : plane_list->old_plane_list) {
238       plane->set_in_use(true);
239     }
240   } else {
241     plane_list->plane_list.swap(plane_list->old_plane_list);
242   }
243 }
244 
Commit(HardwareDisplayPlaneList * plane_list,scoped_refptr<PageFlipRequest> page_flip_request,std::unique_ptr<gfx::GpuFence> * out_fence)245 bool HardwareDisplayPlaneManagerAtomic::Commit(
246     HardwareDisplayPlaneList* plane_list,
247     scoped_refptr<PageFlipRequest> page_flip_request,
248     std::unique_ptr<gfx::GpuFence>* out_fence) {
249   bool test_only = !page_flip_request;
250 
251   std::vector<uint32_t> crtcs = GetCrtcIdsOfPlanes(*plane_list);
252 
253   SetAtomicPropsForCommit(plane_list->atomic_property_set.get(), plane_list,
254                           crtcs, test_only);
255 
256   // After we perform the atomic commit, and if the caller has requested an
257   // out-fence, the out_fence_fds vector will contain any provided out-fence
258   // fds for the crtcs, therefore the scope of out_fence_fds needs to outlive
259   // the CommitProperties call. CommitProperties will write into the Receiver,
260   // and the Receiver's destructor writes into ScopedFD, so we need to ensure
261   // that the Receivers are destructed before we attempt to use their
262   // corresponding ScopedFDs.
263   std::vector<base::ScopedFD> out_fence_fds;
264   {
265     std::vector<base::ScopedFD::Receiver> out_fence_fd_receivers;
266     if (out_fence) {
267       if (!AddOutFencePtrProperties(plane_list->atomic_property_set.get(),
268                                     crtcs, &out_fence_fds,
269                                     &out_fence_fd_receivers)) {
270         ResetCurrentPlaneList(plane_list);
271         return false;
272       }
273     }
274 
275     uint32_t flags =
276         test_only ? DRM_MODE_ATOMIC_TEST_ONLY : DRM_MODE_ATOMIC_NONBLOCK;
277 
278     if (!drm_->CommitProperties(plane_list->atomic_property_set.get(), flags,
279                                 crtcs.size(), page_flip_request)) {
280       if (!test_only) {
281         PLOG(ERROR) << "Failed to commit properties for page flip.";
282       } else {
283         VPLOG(2) << "Failed to commit properties for MODE_ATOMIC_TEST_ONLY.";
284       }
285 
286       ResetCurrentPlaneList(plane_list);
287       return false;
288     }
289   }
290 
291   if (out_fence)
292     *out_fence = CreateMergedGpuFenceFromFDs(std::move(out_fence_fds));
293 
294   plane_list->plane_list.clear();
295   plane_list->atomic_property_set.reset(drmModeAtomicAlloc());
296   return true;
297 }
298 
DisableOverlayPlanes(HardwareDisplayPlaneList * plane_list)299 bool HardwareDisplayPlaneManagerAtomic::DisableOverlayPlanes(
300     HardwareDisplayPlaneList* plane_list) {
301   bool ret = true;
302 
303   if (!plane_list->old_plane_list.empty()) {
304     for (HardwareDisplayPlane* plane : plane_list->old_plane_list) {
305       plane->set_in_use(false);
306       plane->set_owning_crtc(0);
307 
308       HardwareDisplayPlaneAtomic* atomic_plane =
309           static_cast<HardwareDisplayPlaneAtomic*>(plane);
310       atomic_plane->AssignPlaneProps(0, 0, gfx::Rect(), gfx::Rect(),
311                                      gfx::OVERLAY_TRANSFORM_NONE,
312                                      base::kInvalidPlatformFile);
313       atomic_plane->SetPlaneProps(plane_list->atomic_property_set.get());
314     }
315     ret = drm_->CommitProperties(plane_list->atomic_property_set.get(),
316                                  /*flags=*/0, 0 /*unused*/, nullptr);
317     PLOG_IF(ERROR, !ret) << "Failed to commit properties for page flip.";
318   }
319 
320   plane_list->atomic_property_set.reset(drmModeAtomicAlloc());
321   return ret;
322 }
323 
SetColorCorrectionOnAllCrtcPlanes(uint32_t crtc_id,ScopedDrmColorCtmPtr ctm_blob_data)324 bool HardwareDisplayPlaneManagerAtomic::SetColorCorrectionOnAllCrtcPlanes(
325     uint32_t crtc_id,
326     ScopedDrmColorCtmPtr ctm_blob_data) {
327   ScopedDrmAtomicReqPtr property_set(drmModeAtomicAlloc());
328   ScopedDrmPropertyBlob property_blob(
329       drm_->CreatePropertyBlob(ctm_blob_data.get(), sizeof(drm_color_ctm)));
330 
331   const int crtc_index = LookupCrtcIndex(crtc_id);
332   DCHECK_GE(crtc_index, 0);
333 
334   for (auto& plane : planes_) {
335     HardwareDisplayPlaneAtomic* atomic_plane =
336         static_cast<HardwareDisplayPlaneAtomic*>(plane.get());
337 
338     // This assumes planes can only belong to one crtc.
339     if (!atomic_plane->CanUseForCrtc(crtc_index))
340       continue;
341 
342     if (!atomic_plane->SetPlaneCtm(property_set.get(), property_blob->id())) {
343       LOG(ERROR) << "Failed to set PLANE_CTM for plane=" << atomic_plane->id();
344       return false;
345     }
346   }
347 
348   return drm_->CommitProperties(property_set.get(), DRM_MODE_ATOMIC_NONBLOCK, 0,
349                                 nullptr);
350 }
351 
ValidatePrimarySize(const DrmOverlayPlane & primary,const drmModeModeInfo & mode)352 bool HardwareDisplayPlaneManagerAtomic::ValidatePrimarySize(
353     const DrmOverlayPlane& primary,
354     const drmModeModeInfo& mode) {
355   // Atomic KMS allows for primary planes that don't match the size of
356   // the current mode.
357   return true;
358 }
359 
RequestPlanesReadyCallback(DrmOverlayPlaneList planes,base::OnceCallback<void (DrmOverlayPlaneList planes)> callback)360 void HardwareDisplayPlaneManagerAtomic::RequestPlanesReadyCallback(
361     DrmOverlayPlaneList planes,
362     base::OnceCallback<void(DrmOverlayPlaneList planes)> callback) {
363   base::SequencedTaskRunnerHandle::Get()->PostTask(
364       FROM_HERE, base::BindOnce(std::move(callback), std::move(planes)));
365 }
366 
SetPlaneData(HardwareDisplayPlaneList * plane_list,HardwareDisplayPlane * hw_plane,const DrmOverlayPlane & overlay,uint32_t crtc_id,const gfx::Rect & src_rect)367 bool HardwareDisplayPlaneManagerAtomic::SetPlaneData(
368     HardwareDisplayPlaneList* plane_list,
369     HardwareDisplayPlane* hw_plane,
370     const DrmOverlayPlane& overlay,
371     uint32_t crtc_id,
372     const gfx::Rect& src_rect) {
373   HardwareDisplayPlaneAtomic* atomic_plane =
374       static_cast<HardwareDisplayPlaneAtomic*>(hw_plane);
375   uint32_t framebuffer_id = overlay.enable_blend
376                                 ? overlay.buffer->framebuffer_id()
377                                 : overlay.buffer->opaque_framebuffer_id();
378   int fence_fd = base::kInvalidPlatformFile;
379 
380   if (overlay.gpu_fence) {
381     const auto& gpu_fence_handle = overlay.gpu_fence->GetGpuFenceHandle();
382     fence_fd = gpu_fence_handle.owned_fd.get();
383   }
384 
385   if (!atomic_plane->AssignPlaneProps(crtc_id, framebuffer_id,
386                                       overlay.display_bounds, src_rect,
387                                       overlay.plane_transform, fence_fd)) {
388     return false;
389   }
390   return true;
391 }
392 
InitializePlanes()393 bool HardwareDisplayPlaneManagerAtomic::InitializePlanes() {
394   ScopedDrmPlaneResPtr plane_resources = drm_->GetPlaneResources();
395   if (!plane_resources) {
396     PLOG(ERROR) << "Failed to get plane resources.";
397     return false;
398   }
399 
400   for (uint32_t i = 0; i < plane_resources->count_planes; ++i) {
401     std::unique_ptr<HardwareDisplayPlane> plane(
402         CreatePlane(plane_resources->planes[i]));
403 
404     if (plane->Initialize(drm_))
405       planes_.push_back(std::move(plane));
406   }
407 
408   return true;
409 }
410 
411 std::unique_ptr<HardwareDisplayPlane>
CreatePlane(uint32_t plane_id)412 HardwareDisplayPlaneManagerAtomic::CreatePlane(uint32_t plane_id) {
413   return std::make_unique<HardwareDisplayPlaneAtomic>(plane_id);
414 }
415 
CommitColorMatrix(const CrtcProperties & crtc_props)416 bool HardwareDisplayPlaneManagerAtomic::CommitColorMatrix(
417     const CrtcProperties& crtc_props) {
418   DCHECK(crtc_props.ctm.id);
419 #if !defined(COMMIT_PROPERTIES_ON_PAGE_FLIP)
420   ScopedDrmAtomicReqPtr property_set(drmModeAtomicAlloc());
421   int ret = drmModeAtomicAddProperty(property_set.get(), crtc_props.id,
422                                      crtc_props.ctm.id, crtc_props.ctm.value);
423   if (ret < 0) {
424     LOG(ERROR) << "Failed to set CTM property for crtc=" << crtc_props.id;
425     return false;
426   }
427 
428   // If we try to do this in a non-blocking fashion this can return EBUSY since
429   // there is a pending page flip. Do a blocking commit (the same as the legacy
430   // API) to ensure the properties are applied.
431   // TODO(dnicoara): Should cache these values locally and aggregate them with
432   // the page flip event otherwise this "steals" a vsync to apply the property.
433   return drm_->CommitProperties(property_set.get(), 0, 0, nullptr);
434 #else
435   return true;
436 #endif
437 }
438 
CommitGammaCorrection(const CrtcProperties & crtc_props)439 bool HardwareDisplayPlaneManagerAtomic::CommitGammaCorrection(
440     const CrtcProperties& crtc_props) {
441   DCHECK(crtc_props.degamma_lut.id || crtc_props.gamma_lut.id);
442 #if !defined(COMMIT_PROPERTIES_ON_PAGE_FLIP)
443   ScopedDrmAtomicReqPtr property_set(drmModeAtomicAlloc());
444   if (crtc_props.degamma_lut.id) {
445     int ret = drmModeAtomicAddProperty(property_set.get(), crtc_props.id,
446                                        crtc_props.degamma_lut.id,
447                                        crtc_props.degamma_lut.value);
448     if (ret < 0) {
449       LOG(ERROR) << "Failed to set DEGAMMA_LUT property for crtc="
450                  << crtc_props.id;
451       return false;
452     }
453   }
454 
455   if (crtc_props.gamma_lut.id) {
456     int ret = drmModeAtomicAddProperty(property_set.get(), crtc_props.id,
457                                        crtc_props.gamma_lut.id,
458                                        crtc_props.gamma_lut.value);
459     if (ret < 0) {
460       LOG(ERROR) << "Failed to set GAMMA_LUT property for crtc="
461                  << crtc_props.id;
462       return false;
463     }
464   }
465 
466   // If we try to do this in a non-blocking fashion this can return EBUSY since
467   // there is a pending page flip. Do a blocking commit (the same as the legacy
468   // API) to ensure the properties are applied.
469   // TODO(dnicoara): Should cache these values locally and aggregate them with
470   // the page flip event otherwise this "steals" a vsync to apply the property.
471   return drm_->CommitProperties(property_set.get(), 0, 0, nullptr);
472 #else
473   return true;
474 #endif
475 }
476 
AddOutFencePtrProperties(drmModeAtomicReq * property_set,const std::vector<uint32_t> & crtcs,std::vector<base::ScopedFD> * out_fence_fds,std::vector<base::ScopedFD::Receiver> * out_fence_fd_receivers)477 bool HardwareDisplayPlaneManagerAtomic::AddOutFencePtrProperties(
478     drmModeAtomicReq* property_set,
479     const std::vector<uint32_t>& crtcs,
480     std::vector<base::ScopedFD>* out_fence_fds,
481     std::vector<base::ScopedFD::Receiver>* out_fence_fd_receivers) {
482   // Reserve space in vector to ensure no reallocation will take place
483   // and thus all pointers to elements will remain valid
484   DCHECK(out_fence_fds->empty());
485   DCHECK(out_fence_fd_receivers->empty());
486   out_fence_fds->reserve(crtcs.size());
487   out_fence_fd_receivers->reserve(crtcs.size());
488 
489   for (uint32_t crtc : crtcs) {
490     const auto crtc_index = LookupCrtcIndex(crtc);
491     DCHECK_GE(crtc_index, 0);
492     const auto out_fence_ptr_id =
493         crtc_state_[crtc_index].properties.out_fence_ptr.id;
494 
495     if (out_fence_ptr_id > 0) {
496       out_fence_fds->push_back(base::ScopedFD());
497       out_fence_fd_receivers->emplace_back(out_fence_fds->back());
498       // Add the OUT_FENCE_PTR property pointing to the memory location
499       // to save the out-fence fd into for this crtc. Note that
500       // the out-fence fd is produced only after we perform the atomic
501       // commit, so we need to ensure that the pointer remains valid
502       // until then.
503       int ret = drmModeAtomicAddProperty(
504           property_set, crtc, out_fence_ptr_id,
505           reinterpret_cast<uint64_t>(out_fence_fd_receivers->back().get()));
506       if (ret < 0) {
507         LOG(ERROR) << "Failed to set OUT_FENCE_PTR property for crtc=" << crtc
508                    << " error=" << -ret;
509         out_fence_fd_receivers->pop_back();
510         out_fence_fds->pop_back();
511         return false;
512       }
513     }
514   }
515 
516   return true;
517 }
518 
519 }  // namespace ui
520