1 /*
2 * Copyright 2024 Google LLC
3 * SPDX-License-Identifier: MIT
4 */
5
6 #include "GrallocEmulated.h"
7
8 #include <optional>
9 #include <unordered_map>
10
11 #include "drm_fourcc.h"
12 #include "util/log.h"
13
14 namespace gfxstream {
15 namespace {
16
17 static constexpr int numFds = 0;
18 static constexpr int numInts = 1;
19
20 #define DRM_FORMAT_R8_BLOB fourcc_code('9', '9', '9', '9')
21
22 template <typename T, typename N>
DivideRoundUp(T n,N divisor)23 T DivideRoundUp(T n, N divisor) {
24 const T div = static_cast<T>(divisor);
25 const T q = n / div;
26 return n % div == 0 ? q : q + 1;
27 }
28
29 template <typename T, typename N>
Align(T number,N n)30 T Align(T number, N n) {
31 return DivideRoundUp(number, n) * n;
32 }
33
GlFormatToDrmFormat(uint32_t glFormat)34 std::optional<uint32_t> GlFormatToDrmFormat(uint32_t glFormat) {
35 switch (glFormat) {
36 case kGlRGB:
37 return DRM_FORMAT_BGR888;
38 case kGlRGB565:
39 return DRM_FORMAT_BGR565;
40 case kGlRGBA:
41 return DRM_FORMAT_ABGR8888;
42 }
43 return std::nullopt;
44 }
45
AhbToDrmFormat(uint32_t ahbFormat)46 std::optional<uint32_t> AhbToDrmFormat(uint32_t ahbFormat) {
47 switch (ahbFormat) {
48 case GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM:
49 return DRM_FORMAT_ABGR8888;
50 case GFXSTREAM_AHB_FORMAT_R8G8B8X8_UNORM:
51 return DRM_FORMAT_XBGR8888;
52 case GFXSTREAM_AHB_FORMAT_R8G8B8_UNORM:
53 return DRM_FORMAT_BGR888;
54 /*
55 * Confusingly, AHARDWAREBUFFER_FORMAT_RGB_565 is defined as:
56 *
57 * "16-bit packed format that has 5-bit R, 6-bit G, and 5-bit B components, in that
58 * order, from the most-sigfinicant bits to the least-significant bits."
59 *
60 * so the order of the components is intentionally not flipped between the pixel
61 * format and the DRM format.
62 */
63 case GFXSTREAM_AHB_FORMAT_R5G6B5_UNORM:
64 return DRM_FORMAT_RGB565;
65 case GFXSTREAM_AHB_FORMAT_B8G8R8A8_UNORM:
66 return DRM_FORMAT_ARGB8888;
67 case GFXSTREAM_AHB_FORMAT_BLOB:
68 return DRM_FORMAT_R8_BLOB;
69 case GFXSTREAM_AHB_FORMAT_R8_UNORM:
70 return DRM_FORMAT_R8;
71 case GFXSTREAM_AHB_FORMAT_YV12:
72 return DRM_FORMAT_YVU420;
73 case GFXSTREAM_AHB_FORMAT_R16G16B16A16_FLOAT:
74 return DRM_FORMAT_ABGR16161616F;
75 case GFXSTREAM_AHB_FORMAT_R10G10B10A2_UNORM:
76 return DRM_FORMAT_ABGR2101010;
77 case GFXSTREAM_AHB_FORMAT_Y8Cb8Cr8_420:
78 return DRM_FORMAT_NV12;
79 }
80 return std::nullopt;
81 }
82
83 struct DrmFormatPlaneInfo {
84 uint32_t horizontalSubsampling;
85 uint32_t verticalSubsampling;
86 uint32_t bytesPerPixel;
87 };
88 struct DrmFormatInfo {
89 uint32_t androidFormat;
90 uint32_t virglFormat;
91 bool isYuv;
92 uint32_t horizontalAlignmentPixels;
93 uint32_t verticalAlignmentPixels;
94 std::vector<DrmFormatPlaneInfo> planes;
95 };
GetDrmFormatInfoMap()96 const std::unordered_map<uint32_t, DrmFormatInfo>& GetDrmFormatInfoMap() {
97 static const auto* kFormatInfoMap = new std::unordered_map<uint32_t, DrmFormatInfo>({
98 {DRM_FORMAT_ABGR8888,
99 {
100 .androidFormat = GFXSTREAM_AHB_FORMAT_R8G8B8A8_UNORM,
101 .virglFormat = VIRGL_FORMAT_R8G8B8A8_UNORM,
102 .isYuv = false,
103 .horizontalAlignmentPixels = 1,
104 .verticalAlignmentPixels = 1,
105 .planes =
106 {
107 {
108 .horizontalSubsampling = 1,
109 .verticalSubsampling = 1,
110 .bytesPerPixel = 4,
111 },
112 },
113 }},
114 {DRM_FORMAT_ARGB8888,
115 {
116 .androidFormat = GFXSTREAM_AHB_FORMAT_B8G8R8A8_UNORM,
117 .virglFormat = VIRGL_FORMAT_B8G8R8A8_UNORM,
118 .isYuv = false,
119 .horizontalAlignmentPixels = 1,
120 .verticalAlignmentPixels = 1,
121 .planes =
122 {
123 {
124 .horizontalSubsampling = 1,
125 .verticalSubsampling = 1,
126 .bytesPerPixel = 4,
127 },
128 },
129 }},
130 {DRM_FORMAT_BGR888,
131 {
132 .androidFormat = GFXSTREAM_AHB_FORMAT_R8G8B8_UNORM,
133 .virglFormat = VIRGL_FORMAT_R8G8B8_UNORM,
134 .isYuv = false,
135 .horizontalAlignmentPixels = 1,
136 .verticalAlignmentPixels = 1,
137 .planes =
138 {
139 {
140 .horizontalSubsampling = 1,
141 .verticalSubsampling = 1,
142 .bytesPerPixel = 3,
143 },
144 },
145 }},
146 {DRM_FORMAT_BGR565,
147 {
148 .androidFormat = GFXSTREAM_AHB_FORMAT_R5G6B5_UNORM,
149 .virglFormat = VIRGL_FORMAT_B5G6R5_UNORM,
150 .isYuv = false,
151 .horizontalAlignmentPixels = 1,
152 .verticalAlignmentPixels = 1,
153 .planes =
154 {
155 {
156 .horizontalSubsampling = 1,
157 .verticalSubsampling = 1,
158 .bytesPerPixel = 2,
159 },
160 },
161 }},
162 {DRM_FORMAT_R8,
163 {
164 .androidFormat = GFXSTREAM_AHB_FORMAT_R8_UNORM,
165 .virglFormat = VIRGL_FORMAT_R8_UNORM,
166 .isYuv = false,
167 .horizontalAlignmentPixels = 1,
168 .verticalAlignmentPixels = 1,
169 .planes =
170 {
171 {
172 .horizontalSubsampling = 1,
173 .verticalSubsampling = 1,
174 .bytesPerPixel = 1,
175 },
176 },
177 }},
178 {DRM_FORMAT_R8_BLOB,
179 {
180 .androidFormat = GFXSTREAM_AHB_FORMAT_BLOB,
181 .virglFormat = VIRGL_FORMAT_R8_UNORM,
182 .isYuv = false,
183 .horizontalAlignmentPixels = 1,
184 .verticalAlignmentPixels = 1,
185 .planes =
186 {
187 {
188 .horizontalSubsampling = 1,
189 .verticalSubsampling = 1,
190 .bytesPerPixel = 1,
191 },
192 },
193 }},
194 {DRM_FORMAT_ABGR16161616F,
195 {
196 .androidFormat = GFXSTREAM_AHB_FORMAT_R16G16B16A16_FLOAT,
197 .virglFormat = VIRGL_FORMAT_R16G16B16A16_FLOAT,
198 .isYuv = false,
199 .horizontalAlignmentPixels = 1,
200 .verticalAlignmentPixels = 1,
201 .planes =
202 {
203 {
204 .horizontalSubsampling = 1,
205 .verticalSubsampling = 1,
206 .bytesPerPixel = 8,
207 },
208 },
209 }},
210 {DRM_FORMAT_ABGR2101010,
211 {
212 .androidFormat = GFXSTREAM_AHB_FORMAT_R10G10B10A2_UNORM,
213 .virglFormat = VIRGL_FORMAT_R10G10B10A2_UNORM,
214 .isYuv = false,
215 .horizontalAlignmentPixels = 1,
216 .verticalAlignmentPixels = 1,
217 .planes =
218 {
219 {
220 .horizontalSubsampling = 1,
221 .verticalSubsampling = 1,
222 .bytesPerPixel = 4,
223 },
224 },
225 }},
226 {DRM_FORMAT_NV12,
227 {
228 .androidFormat = GFXSTREAM_AHB_FORMAT_Y8Cb8Cr8_420,
229 .virglFormat = VIRGL_FORMAT_NV12,
230 .isYuv = true,
231 .horizontalAlignmentPixels = 2,
232 .verticalAlignmentPixels = 1,
233 .planes =
234 {
235 {
236 .horizontalSubsampling = 1,
237 .verticalSubsampling = 1,
238 .bytesPerPixel = 1,
239 },
240 {
241 .horizontalSubsampling = 2,
242 .verticalSubsampling = 2,
243 .bytesPerPixel = 2,
244 },
245 },
246 }},
247 {DRM_FORMAT_YVU420,
248 {
249 .androidFormat = GFXSTREAM_AHB_FORMAT_YV12,
250 .virglFormat = VIRGL_FORMAT_YV12,
251 .isYuv = true,
252 .horizontalAlignmentPixels = 32,
253 .verticalAlignmentPixels = 1,
254 .planes =
255 {
256 {
257 .horizontalSubsampling = 1,
258 .verticalSubsampling = 1,
259 .bytesPerPixel = 1,
260 },
261 {
262 .horizontalSubsampling = 2,
263 .verticalSubsampling = 2,
264 .bytesPerPixel = 1,
265 },
266 {
267 .horizontalSubsampling = 2,
268 .verticalSubsampling = 2,
269 .bytesPerPixel = 1,
270 },
271 },
272 }},
273 });
274 return *kFormatInfoMap;
275 }
276
277 } // namespace
278
EmulatedAHardwareBuffer(uint32_t width,uint32_t height,uint32_t drmFormat,VirtGpuResourcePtr resource)279 EmulatedAHardwareBuffer::EmulatedAHardwareBuffer(uint32_t width, uint32_t height,
280 uint32_t drmFormat, VirtGpuResourcePtr resource)
281 : mRefCount(1), mWidth(width), mHeight(height), mDrmFormat(drmFormat), mResource(resource) {}
282
~EmulatedAHardwareBuffer()283 EmulatedAHardwareBuffer::~EmulatedAHardwareBuffer() {}
284
getResourceId() const285 uint32_t EmulatedAHardwareBuffer::getResourceId() const { return mResource->getResourceHandle(); }
286
getWidth() const287 uint32_t EmulatedAHardwareBuffer::getWidth() const { return mWidth; }
288
getHeight() const289 uint32_t EmulatedAHardwareBuffer::getHeight() const { return mHeight; }
290
getAndroidFormat() const291 int EmulatedAHardwareBuffer::getAndroidFormat() const {
292 const auto& formatInfosMap = GetDrmFormatInfoMap();
293 auto formatInfoIt = formatInfosMap.find(mDrmFormat);
294 if (formatInfoIt == formatInfosMap.end()) {
295 mesa_loge("Unhandled DRM format:%u", mDrmFormat);
296 return -1;
297 }
298 const auto& formatInfo = formatInfoIt->second;
299 return formatInfo.androidFormat;
300 }
301
getDrmFormat() const302 uint32_t EmulatedAHardwareBuffer::getDrmFormat() const { return mDrmFormat; }
303
asAHardwareBuffer()304 AHardwareBuffer* EmulatedAHardwareBuffer::asAHardwareBuffer() {
305 return reinterpret_cast<AHardwareBuffer*>(this);
306 }
307
asBufferHandle()308 buffer_handle_t EmulatedAHardwareBuffer::asBufferHandle() {
309 return reinterpret_cast<buffer_handle_t>(this);
310 }
311
asEglClientBuffer()312 EGLClientBuffer EmulatedAHardwareBuffer::asEglClientBuffer() {
313 return reinterpret_cast<EGLClientBuffer>(this);
314 }
315
acquire()316 void EmulatedAHardwareBuffer::acquire() { ++mRefCount; }
317
release()318 void EmulatedAHardwareBuffer::release() {
319 --mRefCount;
320 if (mRefCount == 0) {
321 delete this;
322 }
323 }
324
lock(uint8_t ** ptr)325 int EmulatedAHardwareBuffer::lock(uint8_t** ptr) {
326 if (!mMapped) {
327 mMapped = mResource->createMapping();
328 if (!mMapped) {
329 mesa_loge("Failed to lock EmulatedAHardwareBuffer: failed to create mapping.");
330 return -1;
331 }
332
333 mResource->transferFromHost(0, 0, mWidth, mHeight);
334 mResource->wait();
335 }
336
337 *ptr = (*mMapped)->asRawPtr();
338 return 0;
339 }
340
lockPlanes(std::vector<Gralloc::LockedPlane> * ahbPlanes)341 int EmulatedAHardwareBuffer::lockPlanes(std::vector<Gralloc::LockedPlane>* ahbPlanes) {
342 uint8_t* data = 0;
343 int ret = lock(&data);
344 if (ret) {
345 return ret;
346 }
347
348 const auto& formatInfosMap = GetDrmFormatInfoMap();
349 auto formatInfoIt = formatInfosMap.find(mDrmFormat);
350 if (formatInfoIt == formatInfosMap.end()) {
351 mesa_loge("Failed to lock: failed to find format info for drm format:%u", mDrmFormat);
352 return -1;
353 }
354 const auto& formatInfo = formatInfoIt->second;
355
356 const uint32_t alignedWidth = Align(mWidth, formatInfo.horizontalAlignmentPixels);
357 const uint32_t alignedHeight = Align(mHeight, formatInfo.verticalAlignmentPixels);
358 uint32_t cumulativeSize = 0;
359 for (const DrmFormatPlaneInfo& planeInfo : formatInfo.planes) {
360 const uint32_t planeWidth = DivideRoundUp(alignedWidth, planeInfo.horizontalSubsampling);
361 const uint32_t planeHeight = DivideRoundUp(alignedHeight, planeInfo.verticalSubsampling);
362 const uint32_t planeBpp = planeInfo.bytesPerPixel;
363 const uint32_t planeStrideBytes = planeWidth * planeBpp;
364 const uint32_t planeSizeBytes = planeHeight * planeStrideBytes;
365 ahbPlanes->emplace_back(Gralloc::LockedPlane{
366 .data = data + cumulativeSize,
367 .pixelStrideBytes = planeBpp,
368 .rowStrideBytes = planeStrideBytes,
369 });
370 cumulativeSize += planeSizeBytes;
371 }
372
373 if (mDrmFormat == DRM_FORMAT_NV12) {
374 const auto& uPlane = (*ahbPlanes)[1];
375 auto vPlane = uPlane;
376 vPlane.data += 1;
377
378 ahbPlanes->push_back(vPlane);
379 } else if (mDrmFormat == DRM_FORMAT_YVU420) {
380 // Note: lockPlanes() always returns Y, then U, then V but YV12 is Y, then V, then U.
381 auto& plane1 = (*ahbPlanes)[1];
382 auto& plane2 = (*ahbPlanes)[2];
383 std::swap(plane1, plane2);
384 }
385
386 return 0;
387 }
388
unlock()389 int EmulatedAHardwareBuffer::unlock() {
390 if (!mMapped) {
391 mesa_loge("Failed to unlock EmulatedAHardwareBuffer: never locked?");
392 return -1;
393 }
394 mResource->transferToHost(0, 0, mWidth, mHeight);
395 mResource->wait();
396 mMapped.reset();
397 return 0;
398 }
399
EmulatedGralloc(int32_t descriptor)400 EmulatedGralloc::EmulatedGralloc(int32_t descriptor) {
401 mDevice.reset(createPlatformVirtGpuDevice(kCapsetNone, descriptor));
402 }
403
~EmulatedGralloc()404 EmulatedGralloc::~EmulatedGralloc() { mOwned.clear(); }
405
getGrallocType()406 GrallocType EmulatedGralloc::getGrallocType() { return GRALLOC_TYPE_EMULATED; }
407
createColorBuffer(int width,int height,uint32_t glFormat)408 uint32_t EmulatedGralloc::createColorBuffer(int width, int height, uint32_t glFormat) {
409 auto drmFormat = GlFormatToDrmFormat(glFormat);
410 if (!drmFormat) {
411 mesa_loge("Unhandled format");
412 return -1;
413 }
414
415 auto ahb = allocate(width, height, *drmFormat);
416 if (ahb == nullptr) {
417 return -1;
418 }
419
420 EmulatedAHardwareBuffer* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb);
421
422 mOwned.emplace_back(rahb);
423
424 return rahb->getResourceId();
425 }
426
allocate(uint32_t width,uint32_t height,uint32_t ahbFormat,uint64_t usage,AHardwareBuffer ** outputAhb)427 int EmulatedGralloc::allocate(uint32_t width, uint32_t height, uint32_t ahbFormat, uint64_t usage,
428 AHardwareBuffer** outputAhb) {
429 (void)usage;
430
431 auto drmFormat = AhbToDrmFormat(ahbFormat);
432 if (!drmFormat) {
433 mesa_loge("Unhandled AHB format:%u", ahbFormat);
434 return -1;
435 }
436
437 *outputAhb = allocate(width, height, *drmFormat);
438 if (*outputAhb == nullptr) {
439 return -1;
440 }
441
442 return 0;
443 }
444
allocate(uint32_t width,uint32_t height,uint32_t drmFormat)445 AHardwareBuffer* EmulatedGralloc::allocate(uint32_t width, uint32_t height, uint32_t drmFormat) {
446 mesa_loge("Allocating AHB w:%u, h:%u, format %u", width, height, drmFormat);
447
448 const auto& formatInfosMap = GetDrmFormatInfoMap();
449 auto formatInfoIt = formatInfosMap.find(drmFormat);
450 if (formatInfoIt == formatInfosMap.end()) {
451 mesa_loge("Failed to allocate: failed to find format info for drm format:%u", drmFormat);
452 return nullptr;
453 }
454 const auto& formatInfo = formatInfoIt->second;
455
456 const uint32_t alignedWidth = Align(width, formatInfo.horizontalAlignmentPixels);
457 const uint32_t alignedHeight = Align(height, formatInfo.verticalAlignmentPixels);
458 uint32_t stride = 0;
459 uint32_t size = 0;
460 for (uint32_t i = 0; i < formatInfo.planes.size(); i++) {
461 const DrmFormatPlaneInfo& planeInfo = formatInfo.planes[i];
462 const uint32_t planeWidth = DivideRoundUp(alignedWidth, planeInfo.horizontalSubsampling);
463 const uint32_t planeHeight = DivideRoundUp(alignedHeight, planeInfo.verticalSubsampling);
464 const uint32_t planeBpp = planeInfo.bytesPerPixel;
465 const uint32_t planeStrideBytes = planeWidth * planeBpp;
466 const uint32_t planeSizeBytes = planeHeight * planeStrideBytes;
467 size += planeSizeBytes;
468 if (i == 0) stride = planeStrideBytes;
469 }
470
471 const uint32_t bind = (drmFormat == DRM_FORMAT_R8_BLOB || drmFormat == DRM_FORMAT_NV12 ||
472 drmFormat == DRM_FORMAT_YVU420)
473 ? VIRGL_BIND_LINEAR
474 : VIRGL_BIND_RENDER_TARGET;
475
476 auto resource = mDevice->createResource(width, height, stride, size, formatInfo.virglFormat,
477 PIPE_TEXTURE_2D, bind);
478 if (!resource) {
479 mesa_loge("Failed to allocate: failed to create virtio resource.");
480 return nullptr;
481 }
482
483 resource->wait();
484
485 return reinterpret_cast<AHardwareBuffer*>(
486 new EmulatedAHardwareBuffer(width, height, drmFormat, std::move(resource)));
487 }
488
acquire(AHardwareBuffer * ahb)489 void EmulatedGralloc::acquire(AHardwareBuffer* ahb) {
490 auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb);
491 rahb->acquire();
492 }
493
release(AHardwareBuffer * ahb)494 void EmulatedGralloc::release(AHardwareBuffer* ahb) {
495 auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb);
496 rahb->release();
497 }
498
lock(AHardwareBuffer * ahb,uint8_t ** ptr)499 int EmulatedGralloc::lock(AHardwareBuffer* ahb, uint8_t** ptr) {
500 auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb);
501 return rahb->lock(ptr);
502 }
503
lockPlanes(AHardwareBuffer * ahb,std::vector<LockedPlane> * ahbPlanes)504 int EmulatedGralloc::lockPlanes(AHardwareBuffer* ahb, std::vector<LockedPlane>* ahbPlanes) {
505 auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb);
506 return rahb->lockPlanes(ahbPlanes);
507 }
508
unlock(AHardwareBuffer * ahb)509 int EmulatedGralloc::unlock(AHardwareBuffer* ahb) {
510 auto* rahb = reinterpret_cast<EmulatedAHardwareBuffer*>(ahb);
511 return rahb->unlock();
512 }
513
getHostHandle(const native_handle_t * handle)514 uint32_t EmulatedGralloc::getHostHandle(const native_handle_t* handle) {
515 const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle);
516 return ahb->getResourceId();
517 }
518
getHostHandle(const AHardwareBuffer * handle)519 uint32_t EmulatedGralloc::getHostHandle(const AHardwareBuffer* handle) {
520 const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle);
521 return ahb->getResourceId();
522 }
523
getNativeHandle(const AHardwareBuffer * ahb)524 const native_handle_t* EmulatedGralloc::getNativeHandle(const AHardwareBuffer* ahb) {
525 return reinterpret_cast<const native_handle_t*>(ahb);
526 }
527
getFormat(const native_handle_t * handle)528 int EmulatedGralloc::getFormat(const native_handle_t* handle) {
529 const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle);
530 return ahb->getAndroidFormat();
531 }
532
getFormat(const AHardwareBuffer * handle)533 int EmulatedGralloc::getFormat(const AHardwareBuffer* handle) {
534 const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle);
535 return ahb->getAndroidFormat();
536 }
537
getFormatDrmFourcc(const AHardwareBuffer * handle)538 uint32_t EmulatedGralloc::getFormatDrmFourcc(const AHardwareBuffer* handle) {
539 const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle);
540 return ahb->getDrmFormat();
541 }
542
getWidth(const AHardwareBuffer * handle)543 uint32_t EmulatedGralloc::getWidth(const AHardwareBuffer* handle) {
544 const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle);
545 return ahb->getWidth();
546 }
547
getHeight(const AHardwareBuffer * handle)548 uint32_t EmulatedGralloc::getHeight(const AHardwareBuffer* handle) {
549 const auto* ahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(handle);
550 return ahb->getHeight();
551 }
552
getAllocatedSize(const native_handle_t *)553 size_t EmulatedGralloc::getAllocatedSize(const native_handle_t*) {
554 mesa_loge("Unimplemented.");
555 return 0;
556 }
557
getAllocatedSize(const AHardwareBuffer *)558 size_t EmulatedGralloc::getAllocatedSize(const AHardwareBuffer*) {
559 mesa_loge("Unimplemented.");
560 return 0;
561 }
562
getId(const AHardwareBuffer * ahb,uint64_t * id)563 int EmulatedGralloc::getId(const AHardwareBuffer* ahb, uint64_t* id) {
564 const auto* rahb = reinterpret_cast<const EmulatedAHardwareBuffer*>(ahb);
565 *id = rahb->getResourceId();
566 return 0;
567 }
568
createPlatformGralloc(int32_t descriptor)569 Gralloc* createPlatformGralloc(int32_t descriptor) { return new EmulatedGralloc(descriptor); }
570
571 } // namespace gfxstream
572