1 // Copyright 2022 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expresso or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "ColorBuffer.h"
16
17 #if GFXSTREAM_ENABLE_HOST_GLES
18 #include "gl/EmulationGl.h"
19 #endif
20
21 #include "host-common/GfxstreamFatalError.h"
22 #include "host-common/logging.h"
23 #include "vulkan/ColorBufferVk.h"
24 #include "vulkan/VkCommonOperations.h"
25
26 using android::base::ManagedDescriptor;
27 using emugl::ABORT_REASON_OTHER;
28 using emugl::FatalError;
29
30 namespace gfxstream {
31 namespace {
32
33 // ColorBufferVk natively supports YUV images. However, ColorBufferGl
34 // needs to emulate YUV support by having an underlying RGBA texture
35 // and adding in additional YUV<->RGBA conversions when needed. The
36 // memory should not be shared between the VK YUV image and the GL RGBA
37 // texture.
shouldAttemptExternalMemorySharing(FrameworkFormat format)38 bool shouldAttemptExternalMemorySharing(FrameworkFormat format) {
39 return format == FrameworkFormat::FRAMEWORK_FORMAT_GL_COMPATIBLE;
40 }
41
42 } // namespace
43
ColorBuffer(HandleType handle,uint32_t width,uint32_t height,GLenum format,FrameworkFormat frameworkFormat)44 ColorBuffer::ColorBuffer(HandleType handle, uint32_t width, uint32_t height, GLenum format,
45 FrameworkFormat frameworkFormat)
46 : mHandle(handle),
47 mWidth(width),
48 mHeight(height),
49 mFormat(format),
50 mFrameworkFormat(frameworkFormat) {}
51
52 /*static*/
create(gl::EmulationGl * emulationGl,vk::VkEmulation * emulationVk,uint32_t width,uint32_t height,GLenum format,FrameworkFormat frameworkFormat,HandleType handle,android::base::Stream * stream,bool linear)53 std::shared_ptr<ColorBuffer> ColorBuffer::create(gl::EmulationGl* emulationGl,
54 vk::VkEmulation* emulationVk, uint32_t width,
55 uint32_t height, GLenum format,
56 FrameworkFormat frameworkFormat, HandleType handle,
57 android::base::Stream* stream, bool linear) {
58 std::shared_ptr<ColorBuffer> colorBuffer(
59 new ColorBuffer(handle, width, height, format, frameworkFormat));
60
61 if (stream) {
62 // When vk snapshot enabled, mNeedRestore will be touched and set to false immediately.
63 colorBuffer->mNeedRestore = true;
64 }
65 #if GFXSTREAM_ENABLE_HOST_GLES
66 if (emulationGl) {
67 if (stream) {
68 colorBuffer->mColorBufferGl = emulationGl->loadColorBuffer(stream);
69 assert(width == colorBuffer->mColorBufferGl->getWidth());
70 assert(height == colorBuffer->mColorBufferGl->getHeight());
71 assert(frameworkFormat == colorBuffer->mColorBufferGl->getFrameworkFormat());
72 } else {
73 colorBuffer->mColorBufferGl =
74 emulationGl->createColorBuffer(width, height, format, frameworkFormat, handle);
75 }
76 if (!colorBuffer->mColorBufferGl) {
77 ERR("Failed to initialize ColorBufferGl.");
78 return nullptr;
79 }
80 }
81 #endif
82
83 if (emulationVk && emulationVk->live) {
84 const bool vulkanOnly = colorBuffer->mColorBufferGl == nullptr;
85 uint32_t memoryProperty = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
86 if (vulkanOnly && linear) {
87 memoryProperty |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
88 }
89 colorBuffer->mColorBufferVk = vk::ColorBufferVk::create(
90 handle, width, height, format, frameworkFormat, vulkanOnly, memoryProperty, stream);
91 if (!colorBuffer->mColorBufferVk) {
92 if (emulationGl) {
93 // Historically, ColorBufferVk setup was deferred until the first actual Vulkan
94 // usage. This allowed ColorBufferVk setup failures to be unintentionally avoided.
95 } else {
96 ERR("Failed to initialize ColorBufferVk.");
97 return nullptr;
98 }
99 }
100 }
101
102 #if GFXSTREAM_ENABLE_HOST_GLES
103 bool b271028352Workaround = emulationGl && strstr(emulationGl->getGlesRenderer().c_str(), "Intel");
104 bool vkSnapshotEnabled = emulationVk && emulationVk->features.VulkanSnapshots.enabled;
105
106 if ((!stream || vkSnapshotEnabled) && colorBuffer->mColorBufferGl && colorBuffer->mColorBufferVk &&
107 !b271028352Workaround && shouldAttemptExternalMemorySharing(frameworkFormat)) {
108 colorBuffer->touch();
109 auto memoryExport = vk::exportColorBufferMemory(handle);
110 if (memoryExport) {
111 if (colorBuffer->mColorBufferGl->importMemory(
112 std::move(memoryExport->descriptor), memoryExport->size,
113 memoryExport->dedicatedAllocation, memoryExport->linearTiling)) {
114 colorBuffer->mGlAndVkAreSharingExternalMemory = true;
115 } else {
116 ERR("Failed to import memory to ColorBufferGl:%d", handle);
117 }
118 }
119 }
120 #endif
121
122 return colorBuffer;
123 }
124
125 /*static*/
onLoad(gl::EmulationGl * emulationGl,vk::VkEmulation * emulationVk,android::base::Stream * stream)126 std::shared_ptr<ColorBuffer> ColorBuffer::onLoad(gl::EmulationGl* emulationGl,
127 vk::VkEmulation* emulationVk,
128 android::base::Stream* stream) {
129 const auto handle = static_cast<HandleType>(stream->getBe32());
130 const auto width = static_cast<uint32_t>(stream->getBe32());
131 const auto height = static_cast<uint32_t>(stream->getBe32());
132 const auto format = static_cast<GLenum>(stream->getBe32());
133 const auto frameworkFormat = static_cast<FrameworkFormat>(stream->getBe32());
134
135 std::shared_ptr<ColorBuffer> colorBuffer = ColorBuffer::create(
136 emulationGl, emulationVk, width, height, format, frameworkFormat, handle, stream);
137
138 return colorBuffer;
139 }
140
onSave(android::base::Stream * stream)141 void ColorBuffer::onSave(android::base::Stream* stream) {
142 stream->putBe32(getHndl());
143 stream->putBe32(mWidth);
144 stream->putBe32(mHeight);
145 stream->putBe32(static_cast<uint32_t>(mFormat));
146 stream->putBe32(static_cast<uint32_t>(mFrameworkFormat));
147
148 #if GFXSTREAM_ENABLE_HOST_GLES
149 if (mColorBufferGl) {
150 mColorBufferGl->onSave(stream);
151 }
152 #endif
153 if (mColorBufferVk) {
154 mColorBufferVk->onSave(stream);
155 }
156 }
157
restore()158 void ColorBuffer::restore() {
159 #if GFXSTREAM_ENABLE_HOST_GLES
160 if (mColorBufferGl) {
161 mColorBufferGl->restore();
162 }
163 #endif
164 }
165
readToBytes(int x,int y,int width,int height,GLenum pixelsFormat,GLenum pixelsType,void * outPixels)166 void ColorBuffer::readToBytes(int x, int y, int width, int height, GLenum pixelsFormat,
167 GLenum pixelsType, void* outPixels) {
168 touch();
169
170 #if GFXSTREAM_ENABLE_HOST_GLES
171 if (mColorBufferGl) {
172 mColorBufferGl->readPixels(x, y, width, height, pixelsFormat, pixelsType, outPixels);
173 return;
174 }
175 #endif
176
177 if (mColorBufferVk) {
178 mColorBufferVk->readToBytes(x, y, width, height, outPixels);
179 return;
180 }
181
182 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "No ColorBuffer impl?";
183 }
184
readToBytesScaled(int pixelsWidth,int pixelsHeight,GLenum pixelsFormat,GLenum pixelsType,int pixelsRotation,Rect rect,void * outPixels)185 void ColorBuffer::readToBytesScaled(int pixelsWidth, int pixelsHeight, GLenum pixelsFormat,
186 GLenum pixelsType, int pixelsRotation, Rect rect,
187 void* outPixels) {
188 touch();
189
190 #if GFXSTREAM_ENABLE_HOST_GLES
191 if (mColorBufferGl) {
192 mColorBufferGl->readPixelsScaled(pixelsWidth, pixelsHeight, pixelsFormat, pixelsType,
193 pixelsRotation, rect, outPixels);
194 return;
195 }
196 #endif
197
198 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Unimplemented.";
199 }
200
readYuvToBytes(int x,int y,int width,int height,void * outPixels,uint32_t pixelsSize)201 void ColorBuffer::readYuvToBytes(int x, int y, int width, int height, void* outPixels,
202 uint32_t pixelsSize) {
203 touch();
204
205 #if GFXSTREAM_ENABLE_HOST_GLES
206 if (mColorBufferGl) {
207 mColorBufferGl->readPixelsYUVCached(x, y, width, height, outPixels, pixelsSize);
208 return;
209 }
210 #endif
211
212 if (mColorBufferVk) {
213 mColorBufferVk->readToBytes(x, y, width, height, outPixels);
214 return;
215 }
216
217 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "No ColorBuffer impl?";
218 }
219
updateFromBytes(int x,int y,int width,int height,FrameworkFormat frameworkFormat,GLenum pixelsFormat,GLenum pixelsType,const void * pixels,void * metadata)220 bool ColorBuffer::updateFromBytes(int x, int y, int width, int height,
221 FrameworkFormat frameworkFormat, GLenum pixelsFormat,
222 GLenum pixelsType, const void* pixels, void* metadata) {
223 touch();
224
225 #if GFXSTREAM_ENABLE_HOST_GLES
226 if (mColorBufferGl) {
227 mColorBufferGl->subUpdateFromFrameworkFormat(x, y, width, height, frameworkFormat,
228 pixelsFormat, pixelsType, pixels, metadata);
229 return true;
230 }
231 #endif
232
233 if (mColorBufferVk) {
234 return mColorBufferVk->updateFromBytes(x, y, width, height, pixels);
235 }
236
237 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "No ColorBuffer impl?";
238 return false;
239 }
240
updateFromBytes(int x,int y,int width,int height,GLenum pixelsFormat,GLenum pixelsType,const void * pixels)241 bool ColorBuffer::updateFromBytes(int x, int y, int width, int height, GLenum pixelsFormat,
242 GLenum pixelsType, const void* pixels) {
243 touch();
244
245 #if GFXSTREAM_ENABLE_HOST_GLES
246 if (mColorBufferGl) {
247 return mColorBufferGl->subUpdate(x, y, width, height, pixelsFormat, pixelsType, pixels);
248 }
249 #endif
250
251 if (mColorBufferVk) {
252 return mColorBufferVk->updateFromBytes(x, y, width, height, pixels);
253 }
254
255 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "No ColorBuffer impl?";
256 return false;
257 }
258
updateGlFromBytes(const void * bytes,std::size_t bytesSize)259 bool ColorBuffer::updateGlFromBytes(const void* bytes, std::size_t bytesSize) {
260 #if GFXSTREAM_ENABLE_HOST_GLES
261 if (mColorBufferGl) {
262 touch();
263
264 return mColorBufferGl->replaceContents(bytes, bytesSize);
265 }
266 #endif
267
268 return true;
269 }
270
borrowForComposition(UsedApi api,bool isTarget)271 std::unique_ptr<BorrowedImageInfo> ColorBuffer::borrowForComposition(UsedApi api, bool isTarget) {
272 switch (api) {
273 case UsedApi::kGl: {
274 #if GFXSTREAM_ENABLE_HOST_GLES
275 if (!mColorBufferGl) {
276 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
277 }
278 return mColorBufferGl->getBorrowedImageInfo();
279 #endif
280 }
281 case UsedApi::kVk: {
282 if (!mColorBufferVk) {
283 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
284 }
285 return vk::borrowColorBufferForComposition(getHndl(), isTarget);
286 }
287 }
288 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Unimplemented";
289 return nullptr;
290 }
291
borrowForDisplay(UsedApi api)292 std::unique_ptr<BorrowedImageInfo> ColorBuffer::borrowForDisplay(UsedApi api) {
293 switch (api) {
294 case UsedApi::kGl: {
295 #if GFXSTREAM_ENABLE_HOST_GLES
296 if (!mColorBufferGl) {
297 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
298 }
299 return mColorBufferGl->getBorrowedImageInfo();
300 #endif
301 }
302 case UsedApi::kVk: {
303 if (!mColorBufferVk) {
304 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
305 }
306 return vk::borrowColorBufferForDisplay(getHndl());
307 }
308 }
309 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "Unimplemented";
310 return nullptr;
311 }
312
flushFromGl()313 bool ColorBuffer::flushFromGl() {
314 if (!(mColorBufferGl && mColorBufferVk)) {
315 return true;
316 }
317
318 if (mGlAndVkAreSharingExternalMemory) {
319 return true;
320 }
321
322 // ColorBufferGl is currently considered the "main" backing. If this changes,
323 // the "main" should be updated from the current contents of the GL backing.
324 return true;
325 }
326
flushFromVk()327 bool ColorBuffer::flushFromVk() {
328 if (!(mColorBufferGl && mColorBufferVk)) {
329 return true;
330 }
331
332 if (mGlAndVkAreSharingExternalMemory) {
333 return true;
334 }
335 std::vector<uint8_t> contents;
336 if (!vk::readColorBufferToBytes(mHandle, &contents)) {
337 ERR("Failed to get VK contents for ColorBuffer:%d", mHandle);
338 return false;
339 }
340
341 if (contents.empty()) {
342 return false;
343 }
344
345 #if GFXSTREAM_ENABLE_HOST_GLES
346 if (!mColorBufferGl->replaceContents(contents.data(), contents.size())) {
347 ERR("Failed to set GL contents for ColorBuffer:%d", mHandle);
348 return false;
349 }
350 #endif
351
352 return true;
353 }
354
flushFromVkBytes(const void * bytes,size_t bytesSize)355 bool ColorBuffer::flushFromVkBytes(const void* bytes, size_t bytesSize) {
356 if (!(mColorBufferGl && mColorBufferVk)) {
357 return true;
358 }
359
360 if (mGlAndVkAreSharingExternalMemory) {
361 return true;
362 }
363
364 #if GFXSTREAM_ENABLE_HOST_GLES
365 if (mColorBufferGl) {
366 if (!mColorBufferGl->replaceContents(bytes, bytesSize)) {
367 ERR("Failed to update ColorBuffer:%d GL backing from VK bytes.", mHandle);
368 return false;
369 }
370 }
371 #endif
372
373 return true;
374 }
375
invalidateForGl()376 bool ColorBuffer::invalidateForGl() {
377 if (!(mColorBufferGl && mColorBufferVk)) {
378 return true;
379 }
380
381 if (mGlAndVkAreSharingExternalMemory) {
382 return true;
383 }
384
385 // ColorBufferGl is currently considered the "main" backing. If this changes,
386 // the GL backing should be updated from the "main" backing.
387 return true;
388 }
389
invalidateForVk()390 bool ColorBuffer::invalidateForVk() {
391 if (!(mColorBufferGl && mColorBufferVk)) {
392 return true;
393 }
394
395 if (mGlAndVkAreSharingExternalMemory) {
396 return true;
397 }
398
399 #if GFXSTREAM_ENABLE_HOST_GLES
400 std::size_t contentsSize = 0;
401 if (!mColorBufferGl->readContents(&contentsSize, nullptr)) {
402 ERR("Failed to get GL contents size for ColorBuffer:%d", mHandle);
403 return false;
404 }
405
406 std::vector<uint8_t> contents(contentsSize, 0);
407
408 if (!mColorBufferGl->readContents(&contentsSize, contents.data())) {
409 ERR("Failed to get GL contents for ColorBuffer:%d", mHandle);
410 return false;
411 }
412
413 if (!mColorBufferVk->updateFromBytes(contents)) {
414 ERR("Failed to set VK contents for ColorBuffer:%d", mHandle);
415 return false;
416 }
417 #endif
418
419 return true;
420 }
421
importNativeResource(void * nativeResource,uint32_t type,bool preserveContent)422 bool ColorBuffer::importNativeResource(void* nativeResource, uint32_t type, bool preserveContent) {
423 switch (type) {
424 case RESOURCE_TYPE_VK_EXT_MEMORY_HANDLE: {
425 if (mColorBufferGl) {
426 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
427 << "Native resource import type %s is invalid when GL emulation is active. "
428 << "Use RESOURCE_TYPE_EGL_NATIVE_PIXMAP of RESOURCE_TYPE_EGL_IMAGE imports "
429 "instead.";
430 return false;
431 } else if (!mColorBufferVk) {
432 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
433 << "Vulkan emulation must be available for RESOURCE_TYPE_VK_EXT_MEMORY_HANDLE "
434 "import.";
435 return false;
436 }
437 return mColorBufferVk->importExtMemoryHandle(nativeResource, type, preserveContent);
438 }
439 default:
440 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
441 << "Unrecognized type for ColorBuffer::importNativeResource.";
442 return false;
443 }
444 }
445
446 #if GFXSTREAM_ENABLE_HOST_GLES
glOpBlitFromCurrentReadBuffer()447 bool ColorBuffer::glOpBlitFromCurrentReadBuffer() {
448 if (!mColorBufferGl) {
449 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
450 }
451
452 touch();
453
454 return mColorBufferGl->blitFromCurrentReadBuffer();
455 }
456
glOpBindToTexture()457 bool ColorBuffer::glOpBindToTexture() {
458 if (!mColorBufferGl) {
459 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
460 }
461
462 touch();
463
464 return mColorBufferGl->bindToTexture();
465 }
466
glOpBindToTexture2()467 bool ColorBuffer::glOpBindToTexture2() {
468 if (!mColorBufferGl) {
469 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
470 }
471
472 return mColorBufferGl->bindToTexture2();
473 }
474
glOpBindToRenderbuffer()475 bool ColorBuffer::glOpBindToRenderbuffer() {
476 if (!mColorBufferGl) {
477 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
478 }
479
480 touch();
481
482 return mColorBufferGl->bindToRenderbuffer();
483 }
484
glOpGetTexture()485 GLuint ColorBuffer::glOpGetTexture() {
486 if (!mColorBufferGl) {
487 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
488 }
489
490 touch();
491
492 return mColorBufferGl->getTexture();
493 }
494
glOpReadback(unsigned char * img,bool readbackBgra)495 void ColorBuffer::glOpReadback(unsigned char* img, bool readbackBgra) {
496 if (!mColorBufferGl) {
497 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
498 }
499
500 touch();
501
502 return mColorBufferGl->readback(img, readbackBgra);
503 }
504
glOpReadbackAsync(GLuint buffer,bool readbackBgra)505 void ColorBuffer::glOpReadbackAsync(GLuint buffer, bool readbackBgra) {
506 if (!mColorBufferGl) {
507 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
508 }
509
510 touch();
511
512 mColorBufferGl->readbackAsync(buffer, readbackBgra);
513 }
514
glOpImportEglImage(void * image,bool preserveContent)515 bool ColorBuffer::glOpImportEglImage(void* image, bool preserveContent) {
516 if (!mColorBufferGl) {
517 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
518 }
519
520 return mColorBufferGl->importEglImage(image, preserveContent);
521 }
522
glOpImportEglNativePixmap(void * pixmap,bool preserveContent)523 bool ColorBuffer::glOpImportEglNativePixmap(void* pixmap, bool preserveContent) {
524 if (!mColorBufferGl) {
525 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
526 }
527
528 return mColorBufferGl->importEglNativePixmap(pixmap, preserveContent);
529 }
530
glOpSwapYuvTexturesAndUpdate(GLenum format,GLenum type,FrameworkFormat frameworkFormat,GLuint * textures)531 void ColorBuffer::glOpSwapYuvTexturesAndUpdate(GLenum format, GLenum type,
532 FrameworkFormat frameworkFormat, GLuint* textures) {
533 if (!mColorBufferGl) {
534 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
535 }
536
537 mColorBufferGl->swapYUVTextures(frameworkFormat, textures);
538
539 // This makes ColorBufferGl regenerate the RGBA texture using
540 // YUVConverter::drawConvert() with the updated YUV textures.
541 mColorBufferGl->subUpdate(0, 0, mWidth, mHeight, format, type, nullptr);
542
543 flushFromGl();
544 }
545
glOpReadContents(size_t * outNumBytes,void * outContents)546 bool ColorBuffer::glOpReadContents(size_t* outNumBytes, void* outContents) {
547 if (!mColorBufferGl) {
548 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
549 }
550
551 return mColorBufferGl->readContents(outNumBytes, outContents);
552 }
553
glOpIsFastBlitSupported() const554 bool ColorBuffer::glOpIsFastBlitSupported() const {
555 if (!mColorBufferGl) {
556 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
557 }
558
559 return mColorBufferGl->isFastBlitSupported();
560 }
561
glOpPostLayer(const ComposeLayer & l,int frameWidth,int frameHeight)562 void ColorBuffer::glOpPostLayer(const ComposeLayer& l, int frameWidth, int frameHeight) {
563 if (!mColorBufferGl) {
564 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
565 }
566
567 mColorBufferGl->postLayer(l, frameWidth, frameHeight);
568 }
569
glOpPostViewportScaledWithOverlay(float rotation,float dx,float dy)570 void ColorBuffer::glOpPostViewportScaledWithOverlay(float rotation, float dx, float dy) {
571 if (!mColorBufferGl) {
572 GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER)) << "ColorBufferGl not available.";
573 }
574
575 mColorBufferGl->postViewportScaledWithOverlay(rotation, dx, dy);
576 }
577 #endif
578
579 } // namespace gfxstream
580