1 //
2 // Copyright 2016 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // SyncVk.cpp:
7 // Implements the class methods for SyncVk.
8 //
9
10 #include "libANGLE/renderer/vulkan/SyncVk.h"
11
12 #include "common/debug.h"
13 #include "libANGLE/Context.h"
14 #include "libANGLE/Display.h"
15 #include "libANGLE/renderer/vulkan/ContextVk.h"
16 #include "libANGLE/renderer/vulkan/DisplayVk.h"
17
18 #if !defined(ANGLE_PLATFORM_WINDOWS)
19 # include <poll.h>
20 # include <unistd.h>
21 #else
22 # include <io.h>
23 #endif
24
25 namespace
26 {
27 // Wait for file descriptor to be signaled
SyncWaitFd(int fd,uint64_t timeoutNs)28 VkResult SyncWaitFd(int fd, uint64_t timeoutNs)
29 {
30 #if !defined(ANGLE_PLATFORM_WINDOWS)
31 struct pollfd fds;
32 int ret;
33
34 // Convert nanoseconds to milliseconds
35 int timeoutMs = static_cast<int>(timeoutNs / 1000000);
36 // If timeoutNs was non-zero but less than one millisecond, make it a millisecond.
37 if (timeoutNs > 0 && timeoutNs < 1000000)
38 {
39 timeoutMs = 1;
40 }
41
42 ASSERT(fd >= 0);
43
44 fds.fd = fd;
45 fds.events = POLLIN;
46
47 do
48 {
49 ret = poll(&fds, 1, timeoutMs);
50 if (ret > 0)
51 {
52 if (fds.revents & (POLLERR | POLLNVAL))
53 {
54 return VK_ERROR_UNKNOWN;
55 }
56 return VK_SUCCESS;
57 }
58 else if (ret == 0)
59 {
60 return VK_TIMEOUT;
61 }
62 } while (ret == -1 && (errno == EINTR || errno == EAGAIN));
63
64 return VK_ERROR_UNKNOWN;
65 #else
66 UNREACHABLE();
67 return VK_ERROR_UNKNOWN;
68 #endif
69 }
70
71 } // anonymous namespace
72
73 namespace rx
74 {
75 namespace vk
76 {
SyncHelper()77 SyncHelper::SyncHelper() {}
78
~SyncHelper()79 SyncHelper::~SyncHelper() {}
80
releaseToRenderer(RendererVk * renderer)81 void SyncHelper::releaseToRenderer(RendererVk *renderer)
82 {
83 renderer->collectGarbageAndReinit(&mUse, &mEvent);
84 }
85
initialize(ContextVk * contextVk,bool isEglSyncObject)86 angle::Result SyncHelper::initialize(ContextVk *contextVk, bool isEglSyncObject)
87 {
88 ASSERT(!mEvent.valid());
89
90 // Break the current render pass to ensure the proper ordering of the sync object in the
91 // commands.
92 ANGLE_TRY(contextVk->flushCommandsAndEndRenderPass());
93
94 RendererVk *renderer = contextVk->getRenderer();
95 VkDevice device = renderer->getDevice();
96
97 VkEventCreateInfo eventCreateInfo = {};
98 eventCreateInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
99 eventCreateInfo.flags = 0;
100
101 DeviceScoped<Event> event(device);
102 ANGLE_VK_TRY(contextVk, event.get().init(device, eventCreateInfo));
103
104 mEvent = event.release();
105
106 vk::CommandBuffer *commandBuffer;
107 ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
108 commandBuffer->setEvent(mEvent.getHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
109 retain(&contextVk->getResourceUseList());
110
111 if (isEglSyncObject)
112 {
113 contextVk->onEGLSyncHelperInitialize();
114 }
115 else
116 {
117 contextVk->onSyncHelperInitialize();
118 }
119
120 return angle::Result::Continue;
121 }
122
clientWait(Context * context,ContextVk * contextVk,bool flushCommands,uint64_t timeout,VkResult * outResult)123 angle::Result SyncHelper::clientWait(Context *context,
124 ContextVk *contextVk,
125 bool flushCommands,
126 uint64_t timeout,
127 VkResult *outResult)
128 {
129 RendererVk *renderer = context->getRenderer();
130
131 // If the event is already set, don't wait
132 bool alreadySignaled = false;
133 ANGLE_TRY(getStatus(context, &alreadySignaled));
134 if (alreadySignaled)
135 {
136 *outResult = VK_EVENT_SET;
137 return angle::Result::Continue;
138 }
139
140 // We defer (ignore) flushes, so it's possible that the glFence's signal operation is pending
141 // submission.
142 if (contextVk)
143 {
144 if (flushCommands || usedInRecordedCommands())
145 {
146 ANGLE_TRY(contextVk->flushImpl(nullptr));
147 }
148 }
149 else
150 {
151 if (!mUse.getSerial().valid())
152 {
153 // The sync object wasn't flushed before waiting, so the wait will always necessarily
154 // time out.
155 WARN() << "clientWaitSync called without flushing sync object and/or a valid context "
156 "active.";
157 *outResult = VK_TIMEOUT;
158 return angle::Result::Continue;
159 }
160 }
161
162 // If timeout is zero, there's no need to wait, so return timeout already.
163 // Do this after (possibly) flushing, since some apps/tests/traces are relying on this behavior.
164 if (timeout == 0)
165 {
166 *outResult = VK_TIMEOUT;
167 return angle::Result::Continue;
168 }
169
170 ASSERT(mUse.getSerial().valid());
171
172 VkResult status = VK_SUCCESS;
173 ANGLE_TRY(renderer->waitForSerialWithUserTimeout(context, mUse.getSerial(), timeout, &status));
174
175 // Check for errors, but don't consider timeout as such.
176 if (status != VK_TIMEOUT)
177 {
178 ANGLE_VK_TRY(context, status);
179 }
180
181 *outResult = status;
182 return angle::Result::Continue;
183 }
184
serverWait(ContextVk * contextVk)185 angle::Result SyncHelper::serverWait(ContextVk *contextVk)
186 {
187 vk::CommandBuffer *commandBuffer;
188 ANGLE_TRY(contextVk->getOutsideRenderPassCommandBuffer({}, &commandBuffer));
189 commandBuffer->waitEvents(1, mEvent.ptr(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
190 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, nullptr, 0, nullptr, 0,
191 nullptr);
192 retain(&contextVk->getResourceUseList());
193 return angle::Result::Continue;
194 }
195
getStatus(Context * context,bool * signaled) const196 angle::Result SyncHelper::getStatus(Context *context, bool *signaled) const
197 {
198 VkResult result = mEvent.getStatus(context->getDevice());
199 if (result != VK_EVENT_SET && result != VK_EVENT_RESET)
200 {
201 ANGLE_VK_TRY(context, result);
202 }
203 *signaled = (result == VK_EVENT_SET);
204 return angle::Result::Continue;
205 }
206
SyncHelperNativeFence()207 SyncHelperNativeFence::SyncHelperNativeFence() : mNativeFenceFd(kInvalidFenceFd) {}
208
~SyncHelperNativeFence()209 SyncHelperNativeFence::~SyncHelperNativeFence()
210 {
211 if (mNativeFenceFd != kInvalidFenceFd)
212 {
213 close(mNativeFenceFd);
214 }
215 }
216
releaseToRenderer(RendererVk * renderer)217 void SyncHelperNativeFence::releaseToRenderer(RendererVk *renderer)
218 {
219 renderer->collectGarbageAndReinit(&mUse, &mFenceWithFd);
220 }
221
222 // Note: We have mFenceWithFd hold the FD, so that ownership is with ICD. Meanwhile we store a dup
223 // of FD in SyncHelperNativeFence for further reference, i.e. dup of FD. Any call to clientWait
224 // or serverWait will ensure the FD or dup of FD goes to application or ICD. At release, above
225 // it's Garbage collected/destroyed. Otherwise we can't time when to close(fd);
initializeWithFd(ContextVk * contextVk,int inFd)226 angle::Result SyncHelperNativeFence::initializeWithFd(ContextVk *contextVk, int inFd)
227 {
228 ASSERT(inFd >= kInvalidFenceFd);
229
230 // If valid FD provided by application - import it to fence.
231 if (inFd > kInvalidFenceFd)
232 {
233 // File descriptor ownership: EGL_ANDROID_native_fence_sync
234 // Whenever a file descriptor is passed into or returned from an
235 // EGL call in this extension, ownership of that file descriptor is
236 // transferred. The recipient of the file descriptor must close it when it is
237 // no longer needed, and the provider of the file descriptor must dup it
238 // before providing it if they require continued use of the native fence.
239 mNativeFenceFd = inFd;
240 return angle::Result::Continue;
241 }
242
243 RendererVk *renderer = contextVk->getRenderer();
244 VkDevice device = renderer->getDevice();
245
246 DeviceScoped<vk::Fence> fence(device);
247
248 VkExportFenceCreateInfo exportCreateInfo = {};
249 exportCreateInfo.sType = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO;
250 exportCreateInfo.pNext = nullptr;
251 exportCreateInfo.handleTypes = VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR;
252
253 // Create fenceInfo base.
254 VkFenceCreateInfo fenceCreateInfo = {};
255 fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
256 fenceCreateInfo.flags = 0;
257 fenceCreateInfo.pNext = &exportCreateInfo;
258
259 // Initialize/create a VkFence handle
260 ANGLE_VK_TRY(contextVk, fence.get().init(device, fenceCreateInfo));
261
262 // invalid FD provided by application - create one with fence.
263 /*
264 Spec: "When a fence sync object is created or when an EGL native fence sync
265 object is created with the EGL_SYNC_NATIVE_FENCE_FD_ANDROID attribute set to
266 EGL_NO_NATIVE_FENCE_FD_ANDROID, eglCreateSyncKHR also inserts a fence command
267 into the command stream of the bound client API's current context and associates it
268 with the newly created sync object.
269 */
270 // Flush first because the fence comes after current pending set of commands.
271 ANGLE_TRY(contextVk->flushImpl(nullptr));
272
273 retain(&contextVk->getResourceUseList());
274
275 Serial serialOut;
276 // exportFd is exporting VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR type handle which
277 // obeys copy semantics. This means that the fence must already be signaled or the work to
278 // signal it is in the graphics pipeline at the time we export the fd. Thus we need to
279 // EnsureSubmitted here.
280 ANGLE_TRY(renderer->queueSubmitOneOff(
281 contextVk, vk::PrimaryCommandBuffer(), contextVk->hasProtectedContent(),
282 contextVk->getPriority(), &fence.get(), vk::SubmitPolicy::EnsureSubmitted, &serialOut));
283
284 VkFenceGetFdInfoKHR fenceGetFdInfo = {};
285 fenceGetFdInfo.sType = VK_STRUCTURE_TYPE_FENCE_GET_FD_INFO_KHR;
286 fenceGetFdInfo.fence = fence.get().getHandle();
287 fenceGetFdInfo.handleType = VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT_KHR;
288 ANGLE_VK_TRY(contextVk, fence.get().exportFd(device, fenceGetFdInfo, &mNativeFenceFd));
289
290 mFenceWithFd = fence.release();
291
292 return angle::Result::Continue;
293 }
294
clientWait(Context * context,ContextVk * contextVk,bool flushCommands,uint64_t timeout,VkResult * outResult)295 angle::Result SyncHelperNativeFence::clientWait(Context *context,
296 ContextVk *contextVk,
297 bool flushCommands,
298 uint64_t timeout,
299 VkResult *outResult)
300 {
301 RendererVk *renderer = context->getRenderer();
302
303 // If already signaled, don't wait
304 bool alreadySignaled = false;
305 ANGLE_TRY(getStatus(context, &alreadySignaled));
306 if (alreadySignaled)
307 {
308 *outResult = VK_SUCCESS;
309 return angle::Result::Continue;
310 }
311
312 // If timeout is zero, there's no need to wait, so return timeout already.
313 if (timeout == 0)
314 {
315 *outResult = VK_TIMEOUT;
316 return angle::Result::Continue;
317 }
318
319 if (flushCommands && contextVk)
320 {
321 ANGLE_TRY(contextVk->flushImpl(nullptr));
322 }
323
324 VkResult status = VK_SUCCESS;
325 if (mUse.valid())
326 {
327 // We have a valid serial to wait on
328 ANGLE_TRY(
329 renderer->waitForSerialWithUserTimeout(context, mUse.getSerial(), timeout, &status));
330 }
331 else
332 {
333 // We need to wait on the file descriptor
334
335 status = SyncWaitFd(mNativeFenceFd, timeout);
336 if (status != VK_TIMEOUT)
337 {
338 ANGLE_VK_TRY(contextVk, status);
339 }
340 }
341
342 *outResult = status;
343 return angle::Result::Continue;
344 }
345
serverWait(ContextVk * contextVk)346 angle::Result SyncHelperNativeFence::serverWait(ContextVk *contextVk)
347 {
348 RendererVk *renderer = contextVk->getRenderer();
349 VkDevice device = renderer->getDevice();
350
351 DeviceScoped<Semaphore> waitSemaphore(device);
352 // Wait semaphore for next vkQueueSubmit().
353 // Create a Semaphore with imported fenceFd.
354 ANGLE_VK_TRY(contextVk, waitSemaphore.get().init(device));
355
356 VkImportSemaphoreFdInfoKHR importFdInfo = {};
357 importFdInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
358 importFdInfo.semaphore = waitSemaphore.get().getHandle();
359 importFdInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR;
360 importFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT_KHR;
361 importFdInfo.fd = dup(mNativeFenceFd);
362 ANGLE_VK_TRY(contextVk, waitSemaphore.get().importFd(device, importFdInfo));
363
364 // Flush current work, block after current pending commands.
365 ANGLE_TRY(contextVk->flushImpl(nullptr));
366
367 // Add semaphore to next submit job.
368 contextVk->addWaitSemaphore(waitSemaphore.get().getHandle(),
369 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
370 contextVk->addGarbage(&waitSemaphore.get()); // This releases the handle.
371 return angle::Result::Continue;
372 }
373
getStatus(Context * context,bool * signaled) const374 angle::Result SyncHelperNativeFence::getStatus(Context *context, bool *signaled) const
375 {
376 // We've got a serial, check if the serial is still in use
377 if (mUse.valid())
378 {
379 *signaled = !isCurrentlyInUse(context->getRenderer()->getLastCompletedQueueSerial());
380 return angle::Result::Continue;
381 }
382
383 // We don't have a serial, check status of the file descriptor
384 VkResult result = SyncWaitFd(mNativeFenceFd, 0);
385 if (result != VK_TIMEOUT)
386 {
387 ANGLE_VK_TRY(context, result);
388 }
389 *signaled = (result == VK_SUCCESS);
390 return angle::Result::Continue;
391 }
392
dupNativeFenceFD(Context * context,int * fdOut) const393 angle::Result SyncHelperNativeFence::dupNativeFenceFD(Context *context, int *fdOut) const
394 {
395 if (!mFenceWithFd.valid() || mNativeFenceFd == kInvalidFenceFd)
396 {
397 return angle::Result::Stop;
398 }
399
400 *fdOut = dup(mNativeFenceFd);
401
402 return angle::Result::Continue;
403 }
404
405 } // namespace vk
406
SyncVk()407 SyncVk::SyncVk() : SyncImpl() {}
408
~SyncVk()409 SyncVk::~SyncVk() {}
410
onDestroy(const gl::Context * context)411 void SyncVk::onDestroy(const gl::Context *context)
412 {
413 mSyncHelper.releaseToRenderer(vk::GetImpl(context)->getRenderer());
414 }
415
set(const gl::Context * context,GLenum condition,GLbitfield flags)416 angle::Result SyncVk::set(const gl::Context *context, GLenum condition, GLbitfield flags)
417 {
418 ASSERT(condition == GL_SYNC_GPU_COMMANDS_COMPLETE);
419 ASSERT(flags == 0);
420
421 return mSyncHelper.initialize(vk::GetImpl(context), false);
422 }
423
clientWait(const gl::Context * context,GLbitfield flags,GLuint64 timeout,GLenum * outResult)424 angle::Result SyncVk::clientWait(const gl::Context *context,
425 GLbitfield flags,
426 GLuint64 timeout,
427 GLenum *outResult)
428 {
429 ContextVk *contextVk = vk::GetImpl(context);
430
431 ASSERT((flags & ~GL_SYNC_FLUSH_COMMANDS_BIT) == 0);
432
433 bool flush = (flags & GL_SYNC_FLUSH_COMMANDS_BIT) != 0;
434 VkResult result;
435
436 ANGLE_TRY(mSyncHelper.clientWait(contextVk, contextVk, flush, static_cast<uint64_t>(timeout),
437 &result));
438
439 switch (result)
440 {
441 case VK_EVENT_SET:
442 *outResult = GL_ALREADY_SIGNALED;
443 return angle::Result::Continue;
444
445 case VK_SUCCESS:
446 *outResult = GL_CONDITION_SATISFIED;
447 return angle::Result::Continue;
448
449 case VK_TIMEOUT:
450 *outResult = GL_TIMEOUT_EXPIRED;
451 return angle::Result::Incomplete;
452
453 default:
454 UNREACHABLE();
455 *outResult = GL_WAIT_FAILED;
456 return angle::Result::Stop;
457 }
458 }
459
serverWait(const gl::Context * context,GLbitfield flags,GLuint64 timeout)460 angle::Result SyncVk::serverWait(const gl::Context *context, GLbitfield flags, GLuint64 timeout)
461 {
462 ASSERT(flags == 0);
463 ASSERT(timeout == GL_TIMEOUT_IGNORED);
464
465 ContextVk *contextVk = vk::GetImpl(context);
466 return mSyncHelper.serverWait(contextVk);
467 }
468
getStatus(const gl::Context * context,GLint * outResult)469 angle::Result SyncVk::getStatus(const gl::Context *context, GLint *outResult)
470 {
471 ContextVk *contextVk = vk::GetImpl(context);
472 if (contextVk->getShareGroupVk()->isSyncObjectPendingFlush())
473 {
474 ANGLE_TRY(contextVk->flushImpl(nullptr));
475 }
476
477 bool signaled = false;
478 ANGLE_TRY(mSyncHelper.getStatus(contextVk, &signaled));
479
480 *outResult = signaled ? GL_SIGNALED : GL_UNSIGNALED;
481 return angle::Result::Continue;
482 }
483
EGLSyncVk(const egl::AttributeMap & attribs)484 EGLSyncVk::EGLSyncVk(const egl::AttributeMap &attribs)
485 : EGLSyncImpl(), mSyncHelper(nullptr), mAttribs(attribs)
486 {}
487
~EGLSyncVk()488 EGLSyncVk::~EGLSyncVk()
489 {
490 SafeDelete<vk::SyncHelper>(mSyncHelper);
491 }
492
onDestroy(const egl::Display * display)493 void EGLSyncVk::onDestroy(const egl::Display *display)
494 {
495 mSyncHelper->releaseToRenderer(vk::GetImpl(display)->getRenderer());
496 }
497
initialize(const egl::Display * display,const gl::Context * context,EGLenum type)498 egl::Error EGLSyncVk::initialize(const egl::Display *display,
499 const gl::Context *context,
500 EGLenum type)
501 {
502 ASSERT(context != nullptr);
503 mType = type;
504
505 switch (type)
506 {
507 case EGL_SYNC_FENCE_KHR:
508 ASSERT(mAttribs.isEmpty());
509 mSyncHelper = new vk::SyncHelper();
510 if (mSyncHelper->initialize(vk::GetImpl(context), true) == angle::Result::Stop)
511 {
512 return egl::Error(EGL_BAD_ALLOC, "eglCreateSyncKHR failed to create sync object");
513 }
514 return egl::NoError();
515 case EGL_SYNC_NATIVE_FENCE_ANDROID:
516 {
517 vk::SyncHelperNativeFence *syncHelper = new vk::SyncHelperNativeFence();
518 mSyncHelper = syncHelper;
519 int nativeFd = static_cast<EGLint>(mAttribs.getAsInt(EGL_SYNC_NATIVE_FENCE_FD_ANDROID,
520 EGL_NO_NATIVE_FENCE_FD_ANDROID));
521 return angle::ToEGL(syncHelper->initializeWithFd(vk::GetImpl(context), nativeFd),
522 vk::GetImpl(display), EGL_BAD_ALLOC);
523 }
524 default:
525 UNREACHABLE();
526 return egl::Error(EGL_BAD_ALLOC);
527 }
528 }
529
clientWait(const egl::Display * display,const gl::Context * context,EGLint flags,EGLTime timeout,EGLint * outResult)530 egl::Error EGLSyncVk::clientWait(const egl::Display *display,
531 const gl::Context *context,
532 EGLint flags,
533 EGLTime timeout,
534 EGLint *outResult)
535 {
536 ASSERT((flags & ~EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) == 0);
537
538 bool flush = (flags & EGL_SYNC_FLUSH_COMMANDS_BIT_KHR) != 0;
539 VkResult result;
540
541 ContextVk *contextVk = context ? vk::GetImpl(context) : nullptr;
542 if (mSyncHelper->clientWait(vk::GetImpl(display), contextVk, flush,
543 static_cast<uint64_t>(timeout), &result) == angle::Result::Stop)
544 {
545 return egl::Error(EGL_BAD_ALLOC);
546 }
547
548 switch (result)
549 {
550 case VK_EVENT_SET:
551 // fall through. EGL doesn't differentiate between event being already set, or set
552 // before timeout.
553 case VK_SUCCESS:
554 *outResult = EGL_CONDITION_SATISFIED_KHR;
555 return egl::NoError();
556
557 case VK_TIMEOUT:
558 *outResult = EGL_TIMEOUT_EXPIRED_KHR;
559 return egl::NoError();
560
561 default:
562 UNREACHABLE();
563 *outResult = EGL_FALSE;
564 return egl::Error(EGL_BAD_ALLOC);
565 }
566 }
567
serverWait(const egl::Display * display,const gl::Context * context,EGLint flags)568 egl::Error EGLSyncVk::serverWait(const egl::Display *display,
569 const gl::Context *context,
570 EGLint flags)
571 {
572 // Server wait requires a valid bound context.
573 ASSERT(context);
574
575 // No flags are currently implemented.
576 ASSERT(flags == 0);
577
578 DisplayVk *displayVk = vk::GetImpl(display);
579 ContextVk *contextVk = vk::GetImpl(context);
580
581 return angle::ToEGL(mSyncHelper->serverWait(contextVk), displayVk, EGL_BAD_ALLOC);
582 }
583
getStatus(const egl::Display * display,EGLint * outStatus)584 egl::Error EGLSyncVk::getStatus(const egl::Display *display, EGLint *outStatus)
585 {
586 bool signaled = false;
587 if (mSyncHelper->getStatus(vk::GetImpl(display), &signaled) == angle::Result::Stop)
588 {
589 return egl::Error(EGL_BAD_ALLOC);
590 }
591
592 *outStatus = signaled ? EGL_SIGNALED_KHR : EGL_UNSIGNALED_KHR;
593 return egl::NoError();
594 }
595
dupNativeFenceFD(const egl::Display * display,EGLint * fdOut) const596 egl::Error EGLSyncVk::dupNativeFenceFD(const egl::Display *display, EGLint *fdOut) const
597 {
598 switch (mType)
599 {
600 case EGL_SYNC_NATIVE_FENCE_ANDROID:
601 return angle::ToEGL(mSyncHelper->dupNativeFenceFD(vk::GetImpl(display), fdOut),
602 vk::GetImpl(display), EGL_BAD_PARAMETER);
603 default:
604 return egl::EglBadDisplay();
605 }
606 }
607
608 } // namespace rx
609