1 /*
2 * Copyright (C) 2019 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
17 #include "VulkanSurface.h"
18
19 #include <GrDirectContext.h>
20 #include <SkSurface.h>
21 #include <algorithm>
22
23 #include <gui/TraceUtils.h>
24 #include "VulkanManager.h"
25 #include "utils/Color.h"
26
27 #undef LOG_TAG
28 #define LOG_TAG "VulkanSurface"
29
30 namespace android {
31 namespace uirenderer {
32 namespace renderthread {
33
InvertTransform(int transform)34 static int InvertTransform(int transform) {
35 switch (transform) {
36 case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
37 return ANATIVEWINDOW_TRANSFORM_ROTATE_270;
38 case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
39 return ANATIVEWINDOW_TRANSFORM_ROTATE_180;
40 case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
41 return ANATIVEWINDOW_TRANSFORM_ROTATE_90;
42 default:
43 return 0;
44 }
45 }
46
GetPreTransformMatrix(SkISize windowSize,int transform)47 static SkMatrix GetPreTransformMatrix(SkISize windowSize, int transform) {
48 const int width = windowSize.width();
49 const int height = windowSize.height();
50
51 switch (transform) {
52 case 0:
53 return SkMatrix::I();
54 case ANATIVEWINDOW_TRANSFORM_ROTATE_90:
55 return SkMatrix::MakeAll(0, -1, height, 1, 0, 0, 0, 0, 1);
56 case ANATIVEWINDOW_TRANSFORM_ROTATE_180:
57 return SkMatrix::MakeAll(-1, 0, width, 0, -1, height, 0, 0, 1);
58 case ANATIVEWINDOW_TRANSFORM_ROTATE_270:
59 return SkMatrix::MakeAll(0, 1, 0, -1, 0, width, 0, 0, 1);
60 default:
61 LOG_ALWAYS_FATAL("Unsupported Window Transform (%d)", transform);
62 }
63 return SkMatrix::I();
64 }
65
GetPixelSnapMatrix(SkISize windowSize,int transform)66 static SkM44 GetPixelSnapMatrix(SkISize windowSize, int transform) {
67 // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
68 // desired fragment
69 static const SkScalar kOffset = 0.063f;
70 SkMatrix preRotation = GetPreTransformMatrix(windowSize, transform);
71 SkMatrix invert;
72 LOG_ALWAYS_FATAL_IF(!preRotation.invert(&invert));
73 return SkM44::Translate(kOffset, kOffset)
74 .postConcat(SkM44(preRotation))
75 .preConcat(SkM44(invert));
76 }
77
ConnectAndSetWindowDefaults(ANativeWindow * window)78 static bool ConnectAndSetWindowDefaults(ANativeWindow* window) {
79 ATRACE_CALL();
80
81 int err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
82 if (err != 0) {
83 ALOGE("native_window_api_connect failed: %s (%d)", strerror(-err), err);
84 return false;
85 }
86
87 // this will match what we do on GL so pick that here.
88 err = window->setSwapInterval(window, 1);
89 if (err != 0) {
90 ALOGE("native_window->setSwapInterval(1) failed: %s (%d)", strerror(-err), err);
91 return false;
92 }
93
94 err = native_window_set_shared_buffer_mode(window, false);
95 if (err != 0) {
96 ALOGE("native_window_set_shared_buffer_mode(false) failed: %s (%d)", strerror(-err), err);
97 return false;
98 }
99
100 err = native_window_set_auto_refresh(window, false);
101 if (err != 0) {
102 ALOGE("native_window_set_auto_refresh(false) failed: %s (%d)", strerror(-err), err);
103 return false;
104 }
105
106 err = native_window_set_scaling_mode(window, NATIVE_WINDOW_SCALING_MODE_FREEZE);
107 if (err != 0) {
108 ALOGE("native_window_set_scaling_mode(NATIVE_WINDOW_SCALING_MODE_FREEZE) failed: %s (%d)",
109 strerror(-err), err);
110 return false;
111 }
112
113 // Let consumer drive the size of the buffers.
114 err = native_window_set_buffers_dimensions(window, 0, 0);
115 if (err != 0) {
116 ALOGE("native_window_set_buffers_dimensions(0,0) failed: %s (%d)", strerror(-err), err);
117 return false;
118 }
119
120 // Enable auto prerotation, so when buffer size is driven by the consumer
121 // and the transform hint specifies a 90 or 270 degree rotation, the width
122 // and height used for buffer pre-allocation and dequeueBuffer will be
123 // additionally swapped.
124 err = native_window_set_auto_prerotation(window, true);
125 if (err != 0) {
126 ALOGE("VulkanSurface::UpdateWindow() native_window_set_auto_prerotation failed: %s (%d)",
127 strerror(-err), err);
128 return false;
129 }
130
131 return true;
132 }
133
Create(ANativeWindow * window,ColorMode colorMode,SkColorType colorType,sk_sp<SkColorSpace> colorSpace,GrDirectContext * grContext,const VulkanManager & vkManager,uint32_t extraBuffers)134 VulkanSurface* VulkanSurface::Create(ANativeWindow* window, ColorMode colorMode,
135 SkColorType colorType, sk_sp<SkColorSpace> colorSpace,
136 GrDirectContext* grContext, const VulkanManager& vkManager,
137 uint32_t extraBuffers) {
138 // Connect and set native window to default configurations.
139 if (!ConnectAndSetWindowDefaults(window)) {
140 return nullptr;
141 }
142
143 // Initialize WindowInfo struct.
144 WindowInfo windowInfo;
145 if (!InitializeWindowInfoStruct(window, colorMode, colorType, colorSpace, vkManager,
146 extraBuffers, &windowInfo)) {
147 return nullptr;
148 }
149
150 // Now we attempt to modify the window.
151 if (!UpdateWindow(window, windowInfo)) {
152 return nullptr;
153 }
154
155 return new VulkanSurface(window, windowInfo, grContext);
156 }
157
InitializeWindowInfoStruct(ANativeWindow * window,ColorMode colorMode,SkColorType colorType,sk_sp<SkColorSpace> colorSpace,const VulkanManager & vkManager,uint32_t extraBuffers,WindowInfo * outWindowInfo)158 bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode colorMode,
159 SkColorType colorType,
160 sk_sp<SkColorSpace> colorSpace,
161 const VulkanManager& vkManager,
162 uint32_t extraBuffers, WindowInfo* outWindowInfo) {
163 ATRACE_CALL();
164
165 int width, height;
166 int err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
167 if (err != 0 || width < 0) {
168 ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, width);
169 return false;
170 }
171 err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
172 if (err != 0 || height < 0) {
173 ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, height);
174 return false;
175 }
176 outWindowInfo->size = SkISize::Make(width, height);
177
178 int query_value;
179 err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT, &query_value);
180 if (err != 0 || query_value < 0) {
181 ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value);
182 return false;
183 }
184 outWindowInfo->transform = query_value;
185
186 outWindowInfo->actualSize = outWindowInfo->size;
187 if (outWindowInfo->transform & ANATIVEWINDOW_TRANSFORM_ROTATE_90) {
188 outWindowInfo->actualSize.set(outWindowInfo->size.height(), outWindowInfo->size.width());
189 }
190
191 outWindowInfo->preTransform =
192 GetPreTransformMatrix(outWindowInfo->size, outWindowInfo->transform);
193 outWindowInfo->pixelSnapMatrix =
194 GetPixelSnapMatrix(outWindowInfo->size, outWindowInfo->transform);
195
196 err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value);
197 if (err != 0 || query_value < 0) {
198 ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value);
199 return false;
200 }
201 outWindowInfo->bufferCount =
202 static_cast<uint32_t>(query_value) + sTargetBufferCount + extraBuffers;
203
204 err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT, &query_value);
205 if (err != 0 || query_value < 0) {
206 ALOGE("window->query failed: %s (%d) value=%d", strerror(-err), err, query_value);
207 return false;
208 }
209 if (outWindowInfo->bufferCount > static_cast<uint32_t>(query_value)) {
210 // Application must settle for fewer images than desired:
211 outWindowInfo->bufferCount = static_cast<uint32_t>(query_value);
212 }
213
214 outWindowInfo->bufferFormat = ColorTypeToBufferFormat(colorType);
215 outWindowInfo->colorspace = colorSpace;
216 outWindowInfo->dataspace = ColorSpaceToADataSpace(colorSpace.get(), colorType);
217 LOG_ALWAYS_FATAL_IF(
218 outWindowInfo->dataspace == HAL_DATASPACE_UNKNOWN && colorType != kAlpha_8_SkColorType,
219 "Unsupported colorspace");
220
221 VkFormat vkPixelFormat;
222 switch (colorType) {
223 case kRGBA_8888_SkColorType:
224 vkPixelFormat = VK_FORMAT_R8G8B8A8_UNORM;
225 break;
226 case kRGBA_F16_SkColorType:
227 vkPixelFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
228 break;
229 case kRGBA_1010102_SkColorType:
230 vkPixelFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
231 break;
232 case kAlpha_8_SkColorType:
233 vkPixelFormat = VK_FORMAT_R8_UNORM;
234 break;
235 default:
236 LOG_ALWAYS_FATAL("Unsupported colorType: %d", (int)colorType);
237 }
238
239 LOG_ALWAYS_FATAL_IF(nullptr == vkManager.mGetPhysicalDeviceImageFormatProperties2,
240 "vkGetPhysicalDeviceImageFormatProperties2 is missing");
241 VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo;
242 externalImageFormatInfo.sType =
243 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO;
244 externalImageFormatInfo.pNext = nullptr;
245 externalImageFormatInfo.handleType =
246 VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
247
248 VkPhysicalDeviceImageFormatInfo2 imageFormatInfo;
249 imageFormatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
250 imageFormatInfo.pNext = &externalImageFormatInfo;
251 imageFormatInfo.format = vkPixelFormat;
252 imageFormatInfo.type = VK_IMAGE_TYPE_2D;
253 imageFormatInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
254 // Currently Skia requires the images to be color attachments and support all transfer
255 // operations.
256 imageFormatInfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
257 VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
258 imageFormatInfo.flags = 0;
259
260 VkAndroidHardwareBufferUsageANDROID hwbUsage;
261 hwbUsage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID;
262 hwbUsage.pNext = nullptr;
263
264 VkImageFormatProperties2 imgFormProps;
265 imgFormProps.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
266 imgFormProps.pNext = &hwbUsage;
267
268 VkResult res = vkManager.mGetPhysicalDeviceImageFormatProperties2(
269 vkManager.mPhysicalDevice, &imageFormatInfo, &imgFormProps);
270 if (VK_SUCCESS != res) {
271 ALOGE("Failed to query GetPhysicalDeviceImageFormatProperties2");
272 return false;
273 }
274
275 uint64_t consumerUsage;
276 err = native_window_get_consumer_usage(window, &consumerUsage);
277 if (err != 0) {
278 ALOGE("native_window_get_consumer_usage failed: %s (%d)", strerror(-err), err);
279 return false;
280 }
281 outWindowInfo->windowUsageFlags = consumerUsage | hwbUsage.androidHardwareBufferUsage;
282
283 return true;
284 }
285
UpdateWindow(ANativeWindow * window,const WindowInfo & windowInfo)286 bool VulkanSurface::UpdateWindow(ANativeWindow* window, const WindowInfo& windowInfo) {
287 ATRACE_CALL();
288
289 int err = native_window_set_buffers_format(window, windowInfo.bufferFormat);
290 if (err != 0) {
291 ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_format(%d) failed: %s (%d)",
292 windowInfo.bufferFormat, strerror(-err), err);
293 return false;
294 }
295
296 err = native_window_set_buffers_data_space(window, windowInfo.dataspace);
297 if (err != 0) {
298 ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_data_space(%d) "
299 "failed: %s (%d)",
300 windowInfo.dataspace, strerror(-err), err);
301 return false;
302 }
303
304 // native_window_set_buffers_transform() expects the transform the app is requesting that
305 // the compositor perform during composition. With native windows, pre-transform works by
306 // rendering with the same transform the compositor is applying (as in Vulkan), but
307 // then requesting the inverse transform, so that when the compositor does
308 // it's job the two transforms cancel each other out and the compositor ends
309 // up applying an identity transform to the app's buffer.
310 err = native_window_set_buffers_transform(window, InvertTransform(windowInfo.transform));
311 if (err != 0) {
312 ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffers_transform(%d) "
313 "failed: %s (%d)",
314 windowInfo.transform, strerror(-err), err);
315 return false;
316 }
317
318 err = native_window_set_buffer_count(window, windowInfo.bufferCount);
319 if (err != 0) {
320 ALOGE("VulkanSurface::UpdateWindow() native_window_set_buffer_count(%zu) failed: %s (%d)",
321 windowInfo.bufferCount, strerror(-err), err);
322 return false;
323 }
324
325 err = native_window_set_usage(window, windowInfo.windowUsageFlags);
326 if (err != 0) {
327 ALOGE("VulkanSurface::UpdateWindow() native_window_set_usage failed: %s (%d)",
328 strerror(-err), err);
329 return false;
330 }
331
332 return true;
333 }
334
VulkanSurface(ANativeWindow * window,const WindowInfo & windowInfo,GrDirectContext * grContext)335 VulkanSurface::VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo,
336 GrDirectContext* grContext)
337 : mNativeWindow(window), mWindowInfo(windowInfo), mGrContext(grContext) {}
338
~VulkanSurface()339 VulkanSurface::~VulkanSurface() {
340 releaseBuffers();
341
342 // release the native window to be available for use by other clients
343 int err = native_window_api_disconnect(mNativeWindow.get(), NATIVE_WINDOW_API_EGL);
344 ALOGW_IF(err != 0, "native_window_api_disconnect failed: %s (%d)", strerror(-err), err);
345 }
346
releaseBuffers()347 void VulkanSurface::releaseBuffers() {
348 for (uint32_t i = 0; i < mWindowInfo.bufferCount; i++) {
349 VulkanSurface::NativeBufferInfo& bufferInfo = mNativeBuffers[i];
350
351 if (bufferInfo.buffer.get() != nullptr && bufferInfo.dequeued) {
352 int err = mNativeWindow->cancelBuffer(mNativeWindow.get(), bufferInfo.buffer.get(),
353 bufferInfo.dequeue_fence.release());
354 if (err != 0) {
355 ALOGE("cancelBuffer[%u] failed during destroy: %s (%d)", i, strerror(-err), err);
356 }
357 bufferInfo.dequeued = false;
358 bufferInfo.dequeue_fence.reset();
359 }
360
361 LOG_ALWAYS_FATAL_IF(bufferInfo.dequeued);
362 LOG_ALWAYS_FATAL_IF(bufferInfo.dequeue_fence.ok());
363
364 bufferInfo.skSurface.reset();
365 bufferInfo.buffer.clear();
366 bufferInfo.hasValidContents = false;
367 bufferInfo.lastPresentedCount = 0;
368 }
369 }
370
dequeueNativeBuffer()371 VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() {
372 // Set the mCurrentBufferInfo to invalid in case of error and only reset it to the correct
373 // value at the end of the function if everything dequeued correctly.
374 mCurrentBufferInfo = nullptr;
375
376 // Query the transform hint synced from the initial Surface connect or last queueBuffer. The
377 // auto prerotation on the buffer is based on the same transform hint in use by the producer.
378 int transformHint = 0;
379 int err =
380 mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
381
382 // Since auto pre-rotation is enabled, dequeueBuffer to get the consumer driven buffer size
383 // from ANativeWindowBuffer.
384 ANativeWindowBuffer* buffer;
385 base::unique_fd fence_fd;
386 {
387 int rawFd = -1;
388 err = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buffer, &rawFd);
389 fence_fd.reset(rawFd);
390 }
391 if (err != 0) {
392 ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err);
393 return nullptr;
394 }
395
396 SkISize actualSize = SkISize::Make(buffer->width, buffer->height);
397 if (actualSize != mWindowInfo.actualSize || transformHint != mWindowInfo.transform) {
398 if (actualSize != mWindowInfo.actualSize) {
399 // reset the NativeBufferInfo (including SkSurface) associated with the old buffers. The
400 // new NativeBufferInfo storage will be populated lazily as we dequeue each new buffer.
401 mWindowInfo.actualSize = actualSize;
402 releaseBuffers();
403 }
404
405 if (transformHint != mWindowInfo.transform) {
406 err = native_window_set_buffers_transform(mNativeWindow.get(),
407 InvertTransform(transformHint));
408 if (err != 0) {
409 ALOGE("native_window_set_buffers_transform(%d) failed: %s (%d)", transformHint,
410 strerror(-err), err);
411 mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, fence_fd.release());
412 return nullptr;
413 }
414 mWindowInfo.transform = transformHint;
415 }
416
417 mWindowInfo.size = actualSize;
418 if (mWindowInfo.transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
419 mWindowInfo.size.set(actualSize.height(), actualSize.width());
420 }
421
422 mWindowInfo.preTransform = GetPreTransformMatrix(mWindowInfo.size, mWindowInfo.transform);
423 mWindowInfo.pixelSnapMatrix = GetPixelSnapMatrix(mWindowInfo.size, mWindowInfo.transform);
424 }
425
426 uint32_t idx;
427 for (idx = 0; idx < mWindowInfo.bufferCount; idx++) {
428 if (mNativeBuffers[idx].buffer.get() == buffer) {
429 mNativeBuffers[idx].dequeued = true;
430 mNativeBuffers[idx].dequeue_fence = std::move(fence_fd);
431 break;
432 } else if (mNativeBuffers[idx].buffer.get() == nullptr) {
433 // increasing the number of buffers we have allocated
434 mNativeBuffers[idx].buffer = buffer;
435 mNativeBuffers[idx].dequeued = true;
436 mNativeBuffers[idx].dequeue_fence = std::move(fence_fd);
437 break;
438 }
439 }
440 if (idx == mWindowInfo.bufferCount) {
441 ALOGE("dequeueBuffer returned unrecognized buffer");
442 mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, fence_fd.release());
443 return nullptr;
444 }
445
446 VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx];
447
448 if (bufferInfo->skSurface.get() == nullptr) {
449 bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
450 mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
451 kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true);
452 if (bufferInfo->skSurface.get() == nullptr) {
453 ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
454 mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
455 mNativeBuffers[idx].dequeue_fence.release());
456 mNativeBuffers[idx].dequeued = false;
457 return nullptr;
458 }
459 }
460
461 mCurrentBufferInfo = bufferInfo;
462 return bufferInfo;
463 }
464
presentCurrentBuffer(const SkRect & dirtyRect,int semaphoreFd)465 bool VulkanSurface::presentCurrentBuffer(const SkRect& dirtyRect, int semaphoreFd) {
466 if (!dirtyRect.isEmpty()) {
467
468 // native_window_set_surface_damage takes a rectangle in prerotated space
469 // with a bottom-left origin. That is, top > bottom.
470 // The dirtyRect is also in prerotated space, so we just need to switch it to
471 // a bottom-left origin space.
472
473 SkIRect irect;
474 dirtyRect.roundOut(&irect);
475 android_native_rect_t aRect;
476 aRect.left = irect.left();
477 aRect.top = logicalHeight() - irect.top();
478 aRect.right = irect.right();
479 aRect.bottom = logicalHeight() - irect.bottom();
480
481 int err = native_window_set_surface_damage(mNativeWindow.get(), &aRect, 1);
482 ALOGE_IF(err != 0, "native_window_set_surface_damage failed: %s (%d)", strerror(-err), err);
483 }
484
485 LOG_ALWAYS_FATAL_IF(!mCurrentBufferInfo);
486 VulkanSurface::NativeBufferInfo& currentBuffer = *mCurrentBufferInfo;
487 // queueBuffer always closes fence, even on error
488 int queuedFd = (semaphoreFd != -1) ? semaphoreFd : currentBuffer.dequeue_fence.release();
489 int err = mNativeWindow->queueBuffer(mNativeWindow.get(), currentBuffer.buffer.get(), queuedFd);
490
491 currentBuffer.dequeued = false;
492 if (err != 0) {
493 ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err);
494 // cancelBuffer takes ownership of the fence
495 mNativeWindow->cancelBuffer(mNativeWindow.get(), currentBuffer.buffer.get(),
496 currentBuffer.dequeue_fence.release());
497 } else {
498 currentBuffer.hasValidContents = true;
499 currentBuffer.lastPresentedCount = mPresentCount;
500 mPresentCount++;
501 }
502
503 currentBuffer.dequeue_fence.reset();
504
505 return err == 0;
506 }
507
getCurrentBuffersAge()508 int VulkanSurface::getCurrentBuffersAge() {
509 LOG_ALWAYS_FATAL_IF(!mCurrentBufferInfo);
510 VulkanSurface::NativeBufferInfo& currentBuffer = *mCurrentBufferInfo;
511 return currentBuffer.hasValidContents ? (mPresentCount - currentBuffer.lastPresentedCount) : 0;
512 }
513
514 } /* namespace renderthread */
515 } /* namespace uirenderer */
516 } /* namespace android */
517