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