1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include "PostWorker.h"
17
18 #include <string.h>
19
20 #include <chrono>
21
22 #include "ColorBuffer.h"
23 #include "Debug.h"
24 #include "DispatchTables.h"
25 #include "FrameBuffer.h"
26 #include "OpenGLESDispatch/EGLDispatch.h"
27 #include "OpenGLESDispatch/GLESv2Dispatch.h"
28 #include "RenderThreadInfo.h"
29 #include "base/Tracing.h"
30 #include "host-common/GfxstreamFatalError.h"
31 #include "host-common/logging.h"
32 #include "host-common/misc.h"
33 #include "vulkan/VkCommonOperations.h"
34
35 using emugl::ABORT_REASON_OTHER;
36 using emugl::FatalError;
37
38 #define POST_DEBUG 0
39 #if POST_DEBUG >= 1
40 #define DD(fmt, ...) \
41 fprintf(stderr, "%s:%d| " fmt, __func__, __LINE__, ##__VA_ARGS__)
42 #else
43 #define DD(fmt, ...) (void)0
44 #endif
45
46 #define POST_ERROR(fmt, ...) \
47 do { \
48 fprintf(stderr, "%s(%s:%d): " fmt "\n", __func__, __FILE__, __LINE__, \
49 ##__VA_ARGS__); \
50 fflush(stderr); \
51 } while (0)
52
sDefaultRunOnUiThread(UiUpdateFunc f,void * data,bool wait)53 static void sDefaultRunOnUiThread(UiUpdateFunc f, void* data, bool wait) {
54 (void)f;
55 (void)data;
56 (void)wait;
57 }
58
PostWorker(PostWorker::BindSubwinCallback && cb,bool mainThreadPostingOnly,EGLContext eglContext,EGLSurface,DisplayVk * displayVk)59 PostWorker::PostWorker(PostWorker::BindSubwinCallback&& cb, bool mainThreadPostingOnly,
60 EGLContext eglContext, EGLSurface,
61 DisplayVk* displayVk)
62 : mFb(FrameBuffer::getFB()),
63 mBindSubwin(cb),
64 m_mainThreadPostingOnly(mainThreadPostingOnly),
65 m_runOnUiThread(m_mainThreadPostingOnly ? emugl::get_emugl_window_operations().runOnUiThread
66 : sDefaultRunOnUiThread),
67 mContext(eglContext),
68 m_displayVk(displayVk) {}
69
fillMultiDisplayPostStruct(ComposeLayer * l,hwc_rect_t displayArea,hwc_frect_t cropArea,hwc_transform_t transform)70 void PostWorker::fillMultiDisplayPostStruct(ComposeLayer* l,
71 hwc_rect_t displayArea,
72 hwc_frect_t cropArea,
73 hwc_transform_t transform) {
74 l->composeMode = HWC2_COMPOSITION_DEVICE;
75 l->blendMode = HWC2_BLEND_MODE_NONE;
76 l->transform = transform;
77 l->alpha = 1.0;
78 l->displayFrame = displayArea;
79 l->crop = cropArea;
80 }
81
postImpl(ColorBuffer * cb)82 void PostWorker::postImpl(ColorBuffer* cb) {
83 // bind the subwindow eglSurface
84 if (!m_mainThreadPostingOnly && m_needsToRebindWindow) {
85 m_needsToRebindWindow = !mBindSubwin();
86 if (m_needsToRebindWindow) {
87 // Do not proceed if fail to bind to the window.
88 return;
89 }
90 }
91
92 if (m_displayVk) {
93 bool shouldSkip = m_lastVkComposeColorBuffer == cb->getHndl();
94 m_lastVkComposeColorBuffer = std::nullopt;
95 if (shouldSkip) {
96 return;
97 }
98 goldfish_vk::acquireColorBuffersForHostComposing({}, cb->getHndl());
99 auto [success, waitForGpu] = m_displayVk->post(cb->getDisplayBufferVk());
100 goldfish_vk::releaseColorBufferFromHostComposing({cb->getHndl()});
101 if (success) {
102 waitForGpu.wait();
103 } else {
104 m_needsToRebindWindow = true;
105 }
106 return;
107 }
108
109 float dpr = mFb->getDpr();
110 int windowWidth = mFb->windowWidth();
111 int windowHeight = mFb->windowHeight();
112 float px = mFb->getPx();
113 float py = mFb->getPy();
114 int zRot = mFb->getZrot();
115 hwc_transform_t rotation = (hwc_transform_t)0;
116
117 cb->waitSync();
118
119 // Find the x and y values at the origin when "fully scrolled."
120 // Multiply by 2 because the texture goes from -1 to 1, not 0 to 1.
121 // Multiply the windowing coordinates by DPR because they ignore
122 // DPR, but the viewport includes DPR.
123 float fx = 2.f * (m_viewportWidth - windowWidth * dpr) / (float)m_viewportWidth;
124 float fy = 2.f * (m_viewportHeight - windowHeight * dpr) / (float)m_viewportHeight;
125
126 // finally, compute translation values
127 float dx = px * fx;
128 float dy = py * fy;
129
130 if (emugl::get_emugl_multi_display_operations().isMultiDisplayEnabled()) {
131 uint32_t combinedW, combinedH;
132 emugl::get_emugl_multi_display_operations().getCombinedDisplaySize(&combinedW, &combinedH);
133 mFb->getTextureDraw()->prepareForDrawLayer();
134 int32_t start_id = -1, x, y;
135 uint32_t id, w, h, c;
136 while(emugl::get_emugl_multi_display_operations().getNextMultiDisplay(start_id, &id,
137 &x, &y, &w, &h,
138 nullptr, nullptr,
139 &c)) {
140 if ((id != 0) && (w == 0 || h == 0 || c == 0)) {
141 start_id = id;
142 continue;
143 }
144 ColorBuffer* multiDisplayCb = id == 0 ? cb : mFb->findColorBuffer(c).get();
145 if (multiDisplayCb == nullptr) {
146 start_id = id;
147 continue;
148 }
149 ComposeLayer l;
150 hwc_rect_t displayArea = { .left = (int)x,
151 .top = (int)y,
152 .right = (int)(x + w),
153 .bottom = (int)(y + h) };
154 hwc_frect_t cropArea = { .left = 0.0,
155 .top = (float)multiDisplayCb->getHeight(),
156 .right = (float)multiDisplayCb->getWidth(),
157 .bottom = 0.0 };
158 fillMultiDisplayPostStruct(&l, displayArea, cropArea, rotation);
159 multiDisplayCb->postLayer(&l, combinedW, combinedH);
160 start_id = id;
161 }
162 mFb->getTextureDraw()->cleanupForDrawLayer();
163 }
164 else if (emugl::get_emugl_window_operations().isFolded()) {
165 mFb->getTextureDraw()->prepareForDrawLayer();
166 ComposeLayer l;
167 int x, y, w, h;
168 emugl::get_emugl_window_operations().getFoldedArea(&x, &y, &w, &h);
169 hwc_rect_t displayArea = { .left = 0,
170 .top = 0,
171 .right = windowWidth,
172 .bottom = windowHeight };
173 hwc_frect_t cropArea = { .left = (float)x,
174 .top = (float)(y + h),
175 .right = (float)(x + w),
176 .bottom = (float)y };
177 switch ((int)zRot/90) {
178 case 1:
179 rotation = HWC_TRANSFORM_ROT_270;
180 break;
181 case 2:
182 rotation = HWC_TRANSFORM_ROT_180;
183 break;
184 case 3:
185 rotation = HWC_TRANSFORM_ROT_90;
186 break;
187 default: ;
188 }
189
190 fillMultiDisplayPostStruct(&l, displayArea, cropArea, rotation);
191 cb->postLayer(&l, m_viewportWidth/dpr, m_viewportHeight/dpr);
192 mFb->getTextureDraw()->cleanupForDrawLayer();
193 }
194 else {
195 // render the color buffer to the window and apply the overlay
196 GLuint tex = cb->scale();
197 cb->postWithOverlay(tex, zRot, dx, dy);
198 }
199
200 s_egl.eglSwapBuffers(mFb->getDisplay(), mFb->getWindowSurface());
201 }
202
203 // Called whenever the subwindow needs a refresh (FrameBuffer::setupSubWindow).
204 // This rebinds the subwindow context (to account for
205 // when the refresh is a display change, for instance)
206 // and resets the posting viewport.
viewportImpl(int width,int height)207 void PostWorker::viewportImpl(int width, int height) {
208 if (!m_mainThreadPostingOnly) {
209 // For GLES, we rebind the subwindow eglSurface unconditionally: this
210 // could be from a display change, but we want to avoid binding
211 // VkSurfaceKHR too frequently, because that's too expensive.
212 if (!m_displayVk || m_needsToRebindWindow) {
213 m_needsToRebindWindow = !mBindSubwin();
214 if (m_needsToRebindWindow) {
215 // Do not proceed if fail to bind to the window.
216 return;
217 }
218 }
219 }
220
221 if (m_displayVk) {
222 return;
223 }
224
225 float dpr = mFb->getDpr();
226 m_viewportWidth = width * dpr;
227 m_viewportHeight = height * dpr;
228 s_gles2.glViewport(0, 0, m_viewportWidth, m_viewportHeight);
229 }
230
231 // Called when the subwindow refreshes, but there is no
232 // last posted color buffer to show to the user. Instead of
233 // displaying whatever happens to be in the back buffer,
234 // clear() is useful for outputting consistent colors.
clearImpl()235 void PostWorker::clearImpl() {
236 if (m_displayVk) {
237 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
238 << "PostWorker with Vulkan doesn't support clear";
239 }
240 #ifndef __linux__
241 s_gles2.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
242 GL_STENCIL_BUFFER_BIT);
243 s_egl.eglSwapBuffers(mFb->getDisplay(), mFb->getWindowSurface());
244 #endif
245 }
246
composeImpl(const ComposeDevice * p)247 void PostWorker::composeImpl(const ComposeDevice* p) {
248 if (m_displayVk) {
249 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
250 << "PostWorker with Vulkan doesn't support ComposeV1";
251 }
252 // bind the subwindow eglSurface
253 if (!m_mainThreadPostingOnly && m_needsToRebindWindow) {
254 m_needsToRebindWindow = !mBindSubwin();
255 if (m_needsToRebindWindow) {
256 // Do not proceed if fail to bind to the window.
257 return;
258 }
259 }
260
261 GLint vport[4] = { 0, };
262
263 auto cbPtr = mFb->findColorBuffer(p->targetHandle);
264 if (!cbPtr) {
265 s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, 0);
266 s_gles2.glViewport(vport[0], vport[1], vport[2], vport[3]);
267 return;
268 }
269
270 GL_SCOPED_DEBUG_GROUP("PostWorker::composeImpl(into ColorBuffer{hndl:%d tex:%d})", cbPtr->getHndl(), cbPtr->getTexture());
271
272 ComposeLayer* l = (ComposeLayer*)p->layer;
273
274 s_gles2.glGetIntegerv(GL_VIEWPORT, vport);
275 s_gles2.glViewport(0, 0, mFb->getWidth(),mFb->getHeight());
276 if (!m_composeFbo) {
277 s_gles2.glGenFramebuffers(1, &m_composeFbo);
278 }
279 s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, m_composeFbo);
280 s_gles2.glFramebufferTexture2D(GL_FRAMEBUFFER,
281 GL_COLOR_ATTACHMENT0_OES,
282 GL_TEXTURE_2D,
283 cbPtr->getTexture(),
284 0);
285
286 DD("worker compose %d layers\n", p->numLayers);
287 mFb->getTextureDraw()->prepareForDrawLayer();
288 for (int i = 0; i < p->numLayers; i++, l++) {
289 DD("\tcomposeMode %d color %d %d %d %d blendMode "
290 "%d alpha %f transform %d %d %d %d %d "
291 "%f %f %f %f\n",
292 l->composeMode, l->color.r, l->color.g, l->color.b,
293 l->color.a, l->blendMode, l->alpha, l->transform,
294 l->displayFrame.left, l->displayFrame.top,
295 l->displayFrame.right, l->displayFrame.bottom,
296 l->crop.left, l->crop.top, l->crop.right,
297 l->crop.bottom);
298 glesComposeLayer(l, mFb->getWidth(), mFb->getHeight());
299 }
300
301 cbPtr->setSync();
302
303 s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, 0);
304 s_gles2.glViewport(vport[0], vport[1], vport[2], vport[3]);
305 mFb->getTextureDraw()->cleanupForDrawLayer();
306 }
307
composev2Impl(const ComposeDevice_v2 * p)308 std::shared_future<void> PostWorker::composev2Impl(const ComposeDevice_v2* p) {
309 std::shared_future<void> completedFuture =
310 std::async(std::launch::deferred, [] {}).share();
311 completedFuture.wait();
312 // bind the subwindow eglSurface
313 if (!m_mainThreadPostingOnly && m_needsToRebindWindow) {
314 m_needsToRebindWindow = !mBindSubwin();
315 if (m_needsToRebindWindow) {
316 // Do not proceed if fail to bind to the window.
317 return completedFuture;
318 }
319 }
320 ComposeLayer* l = (ComposeLayer*)p->layer;
321 auto targetColorBufferPtr = mFb->findColorBuffer(p->targetHandle);
322
323 if (m_displayVk) {
324 if (!targetColorBufferPtr) {
325 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) <<
326 "Failed to retrieve the composition target buffer";
327 }
328 // We don't copy the render result to the targetHandle color buffer
329 // when using the Vulkan native host swapchain, because we directly
330 // render to the swapchain image instead of rendering onto a
331 // ColorBuffer, and we don't readback from the ColorBuffer so far.
332 std::vector<ColorBufferPtr> cbs; // Keep ColorBuffers alive
333 cbs.emplace_back(targetColorBufferPtr);
334 std::vector<std::shared_ptr<DisplayVk::DisplayBufferInfo>>
335 composeBuffers;
336 std::vector<uint32_t> layerColorBufferHandles;
337 for (int i = 0; i < p->numLayers; ++i) {
338 auto colorBufferPtr = mFb->findColorBuffer(l[i].cbHandle);
339 if (!colorBufferPtr) {
340 composeBuffers.push_back(nullptr);
341 continue;
342 }
343 auto db = colorBufferPtr->getDisplayBufferVk();
344 composeBuffers.push_back(db);
345 if (!db) {
346 continue;
347 }
348 cbs.push_back(colorBufferPtr);
349 layerColorBufferHandles.emplace_back(l[i].cbHandle);
350 }
351 goldfish_vk::acquireColorBuffersForHostComposing(layerColorBufferHandles, p->targetHandle);
352 auto [success, waitForGpu] = m_displayVk->compose(
353 p->numLayers, l, composeBuffers, targetColorBufferPtr->getDisplayBufferVk());
354 goldfish_vk::setColorBufferCurrentLayout(p->targetHandle,
355 VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
356 std::vector<uint32_t> colorBufferHandles(layerColorBufferHandles.begin(),
357 layerColorBufferHandles.end());
358 colorBufferHandles.emplace_back(p->targetHandle);
359 goldfish_vk::releaseColorBufferFromHostComposing(colorBufferHandles);
360 if (!success) {
361 m_needsToRebindWindow = true;
362 waitForGpu = completedFuture;
363 }
364 m_lastVkComposeColorBuffer = p->targetHandle;
365 return waitForGpu;
366 }
367
368 GLint vport[4] = { 0, };
369 s_gles2.glGetIntegerv(GL_VIEWPORT, vport);
370 uint32_t w, h;
371 emugl::get_emugl_multi_display_operations().getMultiDisplay(p->displayId,
372 nullptr,
373 nullptr,
374 &w,
375 &h,
376 nullptr,
377 nullptr,
378 nullptr);
379 s_gles2.glViewport(0, 0, w, h);
380 if (!m_composeFbo) {
381 s_gles2.glGenFramebuffers(1, &m_composeFbo);
382 }
383 s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, m_composeFbo);
384
385 if (!targetColorBufferPtr) {
386 s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, 0);
387 s_gles2.glViewport(vport[0], vport[1], vport[2], vport[3]);
388 return completedFuture;
389 }
390
391 s_gles2.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_OES,
392 GL_TEXTURE_2D,
393 targetColorBufferPtr->getTexture(), 0);
394
395 DD("worker compose %d layers\n", p->numLayers);
396 mFb->getTextureDraw()->prepareForDrawLayer();
397 for (int i = 0; i < p->numLayers; i++, l++) {
398 DD("\tcomposeMode %d color %d %d %d %d blendMode "
399 "%d alpha %f transform %d %d %d %d %d "
400 "%f %f %f %f\n",
401 l->composeMode, l->color.r, l->color.g, l->color.b,
402 l->color.a, l->blendMode, l->alpha, l->transform,
403 l->displayFrame.left, l->displayFrame.top,
404 l->displayFrame.right, l->displayFrame.bottom,
405 l->crop.left, l->crop.top, l->crop.right,
406 l->crop.bottom);
407 glesComposeLayer(l, w, h);
408 }
409
410 targetColorBufferPtr->setSync();
411 s_gles2.glBindFramebuffer(GL_FRAMEBUFFER, 0);
412 s_gles2.glViewport(vport[0], vport[1], vport[2], vport[3]);
413 mFb->getTextureDraw()->cleanupForDrawLayer();
414 return completedFuture;
415 }
416
bind()417 void PostWorker::bind() {
418 if (m_mainThreadPostingOnly && !m_displayVk) {
419 if (mFb->getDisplay() != EGL_NO_DISPLAY) {
420 EGLint res = s_egl.eglMakeCurrent(mFb->getDisplay(), mFb->getWindowSurface(), mFb->getWindowSurface(), mContext);
421 if (!res) fprintf(stderr, "%s: error in binding: 0x%x\n", __func__, s_egl.eglGetError());
422 } else {
423 fprintf(stderr, "%s: no display!\n", __func__);
424 }
425 } else {
426 mBindSubwin();
427 }
428 }
429
unbind()430 void PostWorker::unbind() {
431 if (m_displayVk) {
432 return;
433 }
434 if (mFb->getDisplay() != EGL_NO_DISPLAY) {
435 s_egl.eglMakeCurrent(mFb->getDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE,
436 EGL_NO_CONTEXT);
437 }
438 }
439
glesComposeLayer(ComposeLayer * l,uint32_t w,uint32_t h)440 void PostWorker::glesComposeLayer(ComposeLayer* l, uint32_t w, uint32_t h) {
441 if (m_displayVk) {
442 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) <<
443 "Should not reach with native vulkan swapchain enabled.";
444 }
445 if (l->composeMode == HWC2_COMPOSITION_DEVICE) {
446 ColorBufferPtr cb = mFb->findColorBuffer(l->cbHandle);
447 if (!cb) {
448 // bad colorbuffer handle
449 // ERR("%s: fail to find colorbuffer %d\n", __FUNCTION__, l->cbHandle);
450 return;
451 }
452
453 GL_SCOPED_DEBUG_GROUP("PostWorker::glesComposeLayer(layer ColorBuffer{hndl:%d tex:%d})", cb->getHndl(), cb->getTexture());
454 cb->postLayer(l, w, h);
455 }
456 else {
457 // no Colorbuffer associated with SOLID_COLOR mode
458 mFb->getTextureDraw()->drawLayer(l, w, h, 1, 1, 0);
459 }
460 }
461
screenshot(ColorBuffer * cb,int width,int height,GLenum format,GLenum type,int rotation,void * pixels)462 void PostWorker::screenshot(
463 ColorBuffer* cb,
464 int width,
465 int height,
466 GLenum format,
467 GLenum type,
468 int rotation,
469 void* pixels) {
470 if (m_displayVk) {
471 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) <<
472 "Screenshot not supported with native Vulkan swapchain enabled.";
473 }
474 cb->readPixelsScaled(
475 width, height, format, type, rotation, pixels);
476 }
477
~PostWorker()478 PostWorker::~PostWorker() {
479 if (mFb->getDisplay() != EGL_NO_DISPLAY) {
480 s_egl.eglMakeCurrent(mFb->getDisplay(), EGL_NO_SURFACE, EGL_NO_SURFACE,
481 EGL_NO_CONTEXT);
482 }
483 }
484
post(ColorBuffer * cb)485 void PostWorker::post(ColorBuffer* cb) {
486 runTask(std::packaged_task<void()>([cb, this] { postImpl(cb); }));
487 }
488
viewport(int width,int height)489 void PostWorker::viewport(int width, int height) {
490 runTask(std::packaged_task<void()>(
491 [width, height, this] { viewportImpl(width, height); }));
492 }
493
compose(ComposeDevice * p,uint32_t bufferSize,std::shared_ptr<Post::ComposeCallback> callback)494 void PostWorker::compose(ComposeDevice* p, uint32_t bufferSize,
495 std::shared_ptr<Post::ComposeCallback> callback) {
496 std::vector<char> buffer(bufferSize, 0);
497 memcpy(buffer.data(), p, bufferSize);
498 runTask(std::packaged_task<void()>([buffer = std::move(buffer),
499 callback = std::move(callback), this] {
500 auto completedFuture = std::async(std::launch::deferred, [] {}).share();
501 auto composeDevice =
502 reinterpret_cast<const ComposeDevice*>(buffer.data());
503 if (!isComposeTargetReady(composeDevice->targetHandle)) {
504 ERR("The last composition on the target buffer hasn't completed.");
505 }
506 composeImpl(composeDevice);
507 m_composeTargetToComposeFuture.emplace(composeDevice->targetHandle, completedFuture);
508 (*callback)(completedFuture);
509 }));
510 }
511
compose(ComposeDevice_v2 * p,uint32_t bufferSize,std::shared_ptr<Post::ComposeCallback> callback)512 void PostWorker::compose(ComposeDevice_v2* p, uint32_t bufferSize,
513 std::shared_ptr<Post::ComposeCallback> callback) {
514 std::vector<char> buffer(bufferSize, 0);
515 memcpy(buffer.data(), p, bufferSize);
516 runTask(std::packaged_task<void()>([buffer = std::move(buffer),
517 callback = std::move(callback), this] {
518 auto composeDevice =
519 reinterpret_cast<const ComposeDevice_v2*>(buffer.data());
520 if (!isComposeTargetReady(composeDevice->targetHandle)) {
521 ERR("The last composition on the target buffer hasn't completed.");
522 }
523 auto completedFuture = composev2Impl(composeDevice);
524 m_composeTargetToComposeFuture.emplace(composeDevice->targetHandle, completedFuture);
525 (*callback)(completedFuture);
526 }));
527 }
528
clear()529 void PostWorker::clear() {
530 runTask(std::packaged_task<void()>([this] { clearImpl(); }));
531 }
532
runTask(std::packaged_task<void ()> task)533 void PostWorker::runTask(std::packaged_task<void()> task) {
534 using Task = std::packaged_task<void()>;
535 auto taskPtr = std::make_unique<Task>(std::move(task));
536 if (m_mainThreadPostingOnly) {
537 m_runOnUiThread(
538 [](void* data) {
539 std::unique_ptr<Task> taskPtr(reinterpret_cast<Task*>(data));
540 (*taskPtr)();
541 },
542 taskPtr.release(), false);
543 } else {
544 (*taskPtr)();
545 }
546 }
547
isComposeTargetReady(uint32_t targetHandle)548 bool PostWorker::isComposeTargetReady(uint32_t targetHandle) {
549 // Even if the target ColorBuffer has already been destroyed, the compose future should have
550 // been waited and set to the ready state.
551 for (auto i = m_composeTargetToComposeFuture.begin();
552 i != m_composeTargetToComposeFuture.end();) {
553 auto& composeFuture = i->second;
554 if (composeFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
555 i = m_composeTargetToComposeFuture.erase(i);
556 } else {
557 i++;
558 }
559 }
560 if (m_composeTargetToComposeFuture.find(targetHandle) == m_composeTargetToComposeFuture.end()) {
561 return true;
562 }
563 return false;
564 }
565