1/* 2 * Copyright © 2020 Google, Inc. 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a 5 * copy of this software and associated documentation files (the "Software"), 6 * to deal in the Software without restriction, including without limitation 7 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 * and/or sell copies of the Software, and to permit persons to whom the 9 * Software is furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice (including the next 12 * paragraph) shall be included in all copies or substantial portions of the 13 * Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 */ 23 24#include "tu_private.h" 25 26#include <errno.h> 27#include <fcntl.h> 28#include <stdint.h> 29#include <sys/ioctl.h> 30#include <sys/mman.h> 31 32#include "msm_kgsl.h" 33#include "vk_util.h" 34 35struct tu_syncobj { 36 struct vk_object_base base; 37 uint32_t timestamp; 38 bool timestamp_valid; 39}; 40 41static int 42safe_ioctl(int fd, unsigned long request, void *arg) 43{ 44 int ret; 45 46 do { 47 ret = ioctl(fd, request, arg); 48 } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); 49 50 return ret; 51} 52 53int 54tu_drm_submitqueue_new(const struct tu_device *dev, 55 int priority, 56 uint32_t *queue_id) 57{ 58 struct kgsl_drawctxt_create req = { 59 .flags = KGSL_CONTEXT_SAVE_GMEM | 60 KGSL_CONTEXT_NO_GMEM_ALLOC | 61 KGSL_CONTEXT_PREAMBLE, 62 }; 63 64 int ret = safe_ioctl(dev->physical_device->local_fd, IOCTL_KGSL_DRAWCTXT_CREATE, &req); 65 if (ret) 66 return ret; 67 68 *queue_id = req.drawctxt_id; 69 70 return 0; 71} 72 73void 74tu_drm_submitqueue_close(const struct tu_device *dev, uint32_t queue_id) 75{ 76 struct kgsl_drawctxt_destroy req = { 77 .drawctxt_id = queue_id, 78 }; 79 80 safe_ioctl(dev->physical_device->local_fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &req); 81} 82 83VkResult 84tu_bo_init_new(struct tu_device *dev, struct tu_bo *bo, uint64_t size, 85 enum tu_bo_alloc_flags flags) 86{ 87 struct kgsl_gpumem_alloc_id req = { 88 .size = size, 89 }; 90 91 if (flags & TU_BO_ALLOC_GPU_READ_ONLY) 92 req.flags |= KGSL_MEMFLAGS_GPUREADONLY; 93 94 int ret; 95 96 ret = safe_ioctl(dev->physical_device->local_fd, 97 IOCTL_KGSL_GPUMEM_ALLOC_ID, &req); 98 if (ret) { 99 return vk_errorf(dev, VK_ERROR_OUT_OF_DEVICE_MEMORY, 100 "GPUMEM_ALLOC_ID failed (%s)", strerror(errno)); 101 } 102 103 *bo = (struct tu_bo) { 104 .gem_handle = req.id, 105 .size = req.mmapsize, 106 .iova = req.gpuaddr, 107 }; 108 109 return VK_SUCCESS; 110} 111 112VkResult 113tu_bo_init_dmabuf(struct tu_device *dev, 114 struct tu_bo *bo, 115 uint64_t size, 116 int fd) 117{ 118 struct kgsl_gpuobj_import_dma_buf import_dmabuf = { 119 .fd = fd, 120 }; 121 struct kgsl_gpuobj_import req = { 122 .priv = (uintptr_t)&import_dmabuf, 123 .priv_len = sizeof(import_dmabuf), 124 .flags = 0, 125 .type = KGSL_USER_MEM_TYPE_DMABUF, 126 }; 127 int ret; 128 129 ret = safe_ioctl(dev->physical_device->local_fd, 130 IOCTL_KGSL_GPUOBJ_IMPORT, &req); 131 if (ret) 132 return vk_errorf(dev, VK_ERROR_OUT_OF_DEVICE_MEMORY, 133 "Failed to import dma-buf (%s)\n", strerror(errno)); 134 135 struct kgsl_gpuobj_info info_req = { 136 .id = req.id, 137 }; 138 139 ret = safe_ioctl(dev->physical_device->local_fd, 140 IOCTL_KGSL_GPUOBJ_INFO, &info_req); 141 if (ret) 142 return vk_errorf(dev, VK_ERROR_OUT_OF_DEVICE_MEMORY, 143 "Failed to get dma-buf info (%s)\n", strerror(errno)); 144 145 *bo = (struct tu_bo) { 146 .gem_handle = req.id, 147 .size = info_req.size, 148 .iova = info_req.gpuaddr, 149 }; 150 151 return VK_SUCCESS; 152} 153 154int 155tu_bo_export_dmabuf(struct tu_device *dev, struct tu_bo *bo) 156{ 157 tu_stub(); 158 159 return -1; 160} 161 162VkResult 163tu_bo_map(struct tu_device *dev, struct tu_bo *bo) 164{ 165 if (bo->map) 166 return VK_SUCCESS; 167 168 uint64_t offset = bo->gem_handle << 12; 169 void *map = mmap(0, bo->size, PROT_READ | PROT_WRITE, MAP_SHARED, 170 dev->physical_device->local_fd, offset); 171 if (map == MAP_FAILED) 172 return vk_error(dev, VK_ERROR_MEMORY_MAP_FAILED); 173 174 bo->map = map; 175 176 return VK_SUCCESS; 177} 178 179void 180tu_bo_finish(struct tu_device *dev, struct tu_bo *bo) 181{ 182 assert(bo->gem_handle); 183 184 if (bo->map) 185 munmap(bo->map, bo->size); 186 187 struct kgsl_gpumem_free_id req = { 188 .id = bo->gem_handle 189 }; 190 191 safe_ioctl(dev->physical_device->local_fd, IOCTL_KGSL_GPUMEM_FREE_ID, &req); 192} 193 194static VkResult 195get_kgsl_prop(int fd, unsigned int type, void *value, size_t size) 196{ 197 struct kgsl_device_getproperty getprop = { 198 .type = type, 199 .value = value, 200 .sizebytes = size, 201 }; 202 203 return safe_ioctl(fd, IOCTL_KGSL_DEVICE_GETPROPERTY, &getprop); 204} 205 206VkResult 207tu_enumerate_devices(struct tu_instance *instance) 208{ 209 static const char path[] = "/dev/kgsl-3d0"; 210 int fd; 211 212 struct tu_physical_device *device = &instance->physical_devices[0]; 213 214 if (instance->vk.enabled_extensions.KHR_display) 215 return vk_errorf(instance, VK_ERROR_INCOMPATIBLE_DRIVER, 216 "I can't KHR_display"); 217 218 fd = open(path, O_RDWR | O_CLOEXEC); 219 if (fd < 0) { 220 instance->physical_device_count = 0; 221 return vk_errorf(instance, VK_ERROR_INCOMPATIBLE_DRIVER, 222 "failed to open device %s", path); 223 } 224 225 struct kgsl_devinfo info; 226 if (get_kgsl_prop(fd, KGSL_PROP_DEVICE_INFO, &info, sizeof(info))) 227 goto fail; 228 229 uint64_t gmem_iova; 230 if (get_kgsl_prop(fd, KGSL_PROP_UCHE_GMEM_VADDR, &gmem_iova, sizeof(gmem_iova))) 231 goto fail; 232 233 /* kgsl version check? */ 234 235 if (instance->debug_flags & TU_DEBUG_STARTUP) 236 mesa_logi("Found compatible device '%s'.", path); 237 238 device->instance = instance; 239 device->master_fd = -1; 240 device->local_fd = fd; 241 242 device->dev_id.gpu_id = 243 ((info.chip_id >> 24) & 0xff) * 100 + 244 ((info.chip_id >> 16) & 0xff) * 10 + 245 ((info.chip_id >> 8) & 0xff); 246 device->dev_id.chip_id = info.chip_id; 247 device->gmem_size = info.gmem_sizebytes; 248 device->gmem_base = gmem_iova; 249 250 device->heap.size = tu_get_system_heap_size(); 251 device->heap.used = 0u; 252 device->heap.flags = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT; 253 254 if (tu_physical_device_init(device, instance) != VK_SUCCESS) 255 goto fail; 256 257 instance->physical_device_count = 1; 258 259 return VK_SUCCESS; 260 261fail: 262 close(fd); 263 return VK_ERROR_INITIALIZATION_FAILED; 264} 265 266static int 267timestamp_to_fd(struct tu_queue *queue, uint32_t timestamp) 268{ 269 int fd; 270 struct kgsl_timestamp_event event = { 271 .type = KGSL_TIMESTAMP_EVENT_FENCE, 272 .context_id = queue->msm_queue_id, 273 .timestamp = timestamp, 274 .priv = &fd, 275 .len = sizeof(fd), 276 }; 277 278 int ret = safe_ioctl(queue->device->fd, IOCTL_KGSL_TIMESTAMP_EVENT, &event); 279 if (ret) 280 return -1; 281 282 return fd; 283} 284 285/* return true if timestamp a is greater (more recent) then b 286 * this relies on timestamps never having a difference > (1<<31) 287 */ 288static inline bool 289timestamp_cmp(uint32_t a, uint32_t b) 290{ 291 return (int32_t) (a - b) >= 0; 292} 293 294static uint32_t 295max_ts(uint32_t a, uint32_t b) 296{ 297 return timestamp_cmp(a, b) ? a : b; 298} 299 300static uint32_t 301min_ts(uint32_t a, uint32_t b) 302{ 303 return timestamp_cmp(a, b) ? b : a; 304} 305 306static struct tu_syncobj 307sync_merge(const VkSemaphore *syncobjs, uint32_t count, bool wait_all, bool reset) 308{ 309 struct tu_syncobj ret; 310 311 ret.timestamp_valid = false; 312 313 for (uint32_t i = 0; i < count; ++i) { 314 TU_FROM_HANDLE(tu_syncobj, sync, syncobjs[i]); 315 316 /* TODO: this means the fence is unsignaled and will never become signaled */ 317 if (!sync->timestamp_valid) 318 continue; 319 320 if (!ret.timestamp_valid) 321 ret.timestamp = sync->timestamp; 322 else if (wait_all) 323 ret.timestamp = max_ts(ret.timestamp, sync->timestamp); 324 else 325 ret.timestamp = min_ts(ret.timestamp, sync->timestamp); 326 327 ret.timestamp_valid = true; 328 if (reset) 329 sync->timestamp_valid = false; 330 331 } 332 return ret; 333} 334 335VKAPI_ATTR VkResult VKAPI_CALL 336tu_QueueSubmit(VkQueue _queue, 337 uint32_t submitCount, 338 const VkSubmitInfo *pSubmits, 339 VkFence _fence) 340{ 341 TU_FROM_HANDLE(tu_queue, queue, _queue); 342 TU_FROM_HANDLE(tu_syncobj, fence, _fence); 343 VkResult result = VK_SUCCESS; 344 345 uint32_t max_entry_count = 0; 346 for (uint32_t i = 0; i < submitCount; ++i) { 347 const VkSubmitInfo *submit = pSubmits + i; 348 349 const VkPerformanceQuerySubmitInfoKHR *perf_info = 350 vk_find_struct_const(pSubmits[i].pNext, 351 PERFORMANCE_QUERY_SUBMIT_INFO_KHR); 352 353 uint32_t entry_count = 0; 354 for (uint32_t j = 0; j < submit->commandBufferCount; ++j) { 355 TU_FROM_HANDLE(tu_cmd_buffer, cmdbuf, submit->pCommandBuffers[j]); 356 entry_count += cmdbuf->cs.entry_count; 357 if (perf_info) 358 entry_count++; 359 } 360 361 max_entry_count = MAX2(max_entry_count, entry_count); 362 } 363 364 struct kgsl_command_object *cmds = 365 vk_alloc(&queue->device->vk.alloc, 366 sizeof(cmds[0]) * max_entry_count, 8, 367 VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); 368 if (cmds == NULL) 369 return vk_error(queue, VK_ERROR_OUT_OF_HOST_MEMORY); 370 371 for (uint32_t i = 0; i < submitCount; ++i) { 372 const VkSubmitInfo *submit = pSubmits + i; 373 uint32_t entry_idx = 0; 374 const VkPerformanceQuerySubmitInfoKHR *perf_info = 375 vk_find_struct_const(pSubmits[i].pNext, 376 PERFORMANCE_QUERY_SUBMIT_INFO_KHR); 377 378 379 for (uint32_t j = 0; j < submit->commandBufferCount; j++) { 380 TU_FROM_HANDLE(tu_cmd_buffer, cmdbuf, submit->pCommandBuffers[j]); 381 struct tu_cs *cs = &cmdbuf->cs; 382 383 if (perf_info) { 384 struct tu_cs_entry *perf_cs_entry = 385 &cmdbuf->device->perfcntrs_pass_cs_entries[perf_info->counterPassIndex]; 386 387 cmds[entry_idx++] = (struct kgsl_command_object) { 388 .offset = perf_cs_entry->offset, 389 .gpuaddr = perf_cs_entry->bo->iova, 390 .size = perf_cs_entry->size, 391 .flags = KGSL_CMDLIST_IB, 392 .id = perf_cs_entry->bo->gem_handle, 393 }; 394 } 395 396 for (unsigned k = 0; k < cs->entry_count; k++) { 397 cmds[entry_idx++] = (struct kgsl_command_object) { 398 .offset = cs->entries[k].offset, 399 .gpuaddr = cs->entries[k].bo->iova, 400 .size = cs->entries[k].size, 401 .flags = KGSL_CMDLIST_IB, 402 .id = cs->entries[k].bo->gem_handle, 403 }; 404 } 405 } 406 407 struct tu_syncobj s = sync_merge(submit->pWaitSemaphores, 408 submit->waitSemaphoreCount, 409 true, true); 410 411 struct kgsl_cmd_syncpoint_timestamp ts = { 412 .context_id = queue->msm_queue_id, 413 .timestamp = s.timestamp, 414 }; 415 struct kgsl_command_syncpoint sync = { 416 .type = KGSL_CMD_SYNCPOINT_TYPE_TIMESTAMP, 417 .size = sizeof(ts), 418 .priv = (uintptr_t) &ts, 419 }; 420 421 struct kgsl_gpu_command req = { 422 .flags = KGSL_CMDBATCH_SUBMIT_IB_LIST, 423 .context_id = queue->msm_queue_id, 424 .cmdlist = (uint64_t) (uintptr_t) cmds, 425 .numcmds = entry_idx, 426 .cmdsize = sizeof(struct kgsl_command_object), 427 .synclist = (uintptr_t) &sync, 428 .syncsize = sizeof(struct kgsl_command_syncpoint), 429 .numsyncs = s.timestamp_valid ? 1 : 0, 430 }; 431 432 int ret = safe_ioctl(queue->device->physical_device->local_fd, 433 IOCTL_KGSL_GPU_COMMAND, &req); 434 if (ret) { 435 result = tu_device_set_lost(queue->device, 436 "submit failed: %s\n", strerror(errno)); 437 goto fail; 438 } 439 440 for (uint32_t i = 0; i < submit->signalSemaphoreCount; i++) { 441 TU_FROM_HANDLE(tu_syncobj, sem, submit->pSignalSemaphores[i]); 442 sem->timestamp = req.timestamp; 443 sem->timestamp_valid = true; 444 } 445 446 /* no need to merge fences as queue execution is serialized */ 447 if (i == submitCount - 1) { 448 int fd = timestamp_to_fd(queue, req.timestamp); 449 if (fd < 0) { 450 result = tu_device_set_lost(queue->device, 451 "Failed to create sync file for timestamp: %s\n", 452 strerror(errno)); 453 goto fail; 454 } 455 456 if (queue->fence >= 0) 457 close(queue->fence); 458 queue->fence = fd; 459 460 if (fence) { 461 fence->timestamp = req.timestamp; 462 fence->timestamp_valid = true; 463 } 464 } 465 } 466fail: 467 vk_free(&queue->device->vk.alloc, cmds); 468 469 return result; 470} 471 472static VkResult 473sync_create(VkDevice _device, 474 bool signaled, 475 bool fence, 476 const VkAllocationCallbacks *pAllocator, 477 void **p_sync) 478{ 479 TU_FROM_HANDLE(tu_device, device, _device); 480 481 struct tu_syncobj *sync = 482 vk_object_alloc(&device->vk, pAllocator, sizeof(*sync), 483 fence ? VK_OBJECT_TYPE_FENCE : VK_OBJECT_TYPE_SEMAPHORE); 484 if (!sync) 485 return vk_error(device, VK_ERROR_OUT_OF_HOST_MEMORY); 486 487 if (signaled) 488 tu_finishme("CREATE FENCE SIGNALED"); 489 490 sync->timestamp_valid = false; 491 *p_sync = sync; 492 493 return VK_SUCCESS; 494} 495 496VKAPI_ATTR VkResult VKAPI_CALL 497tu_ImportSemaphoreFdKHR(VkDevice _device, 498 const VkImportSemaphoreFdInfoKHR *pImportSemaphoreFdInfo) 499{ 500 tu_finishme("ImportSemaphoreFdKHR"); 501 return VK_SUCCESS; 502} 503 504VKAPI_ATTR VkResult VKAPI_CALL 505tu_GetSemaphoreFdKHR(VkDevice _device, 506 const VkSemaphoreGetFdInfoKHR *pGetFdInfo, 507 int *pFd) 508{ 509 tu_finishme("GetSemaphoreFdKHR"); 510 return VK_SUCCESS; 511} 512 513VKAPI_ATTR VkResult VKAPI_CALL 514tu_CreateSemaphore(VkDevice device, 515 const VkSemaphoreCreateInfo *pCreateInfo, 516 const VkAllocationCallbacks *pAllocator, 517 VkSemaphore *pSemaphore) 518{ 519 return sync_create(device, false, false, pAllocator, (void**) pSemaphore); 520} 521 522VKAPI_ATTR void VKAPI_CALL 523tu_DestroySemaphore(VkDevice _device, 524 VkSemaphore semaphore, 525 const VkAllocationCallbacks *pAllocator) 526{ 527 TU_FROM_HANDLE(tu_device, device, _device); 528 TU_FROM_HANDLE(tu_syncobj, sync, semaphore); 529 530 if (!sync) 531 return; 532 533 vk_object_free(&device->vk, pAllocator, sync); 534} 535 536VKAPI_ATTR VkResult VKAPI_CALL 537tu_ImportFenceFdKHR(VkDevice _device, 538 const VkImportFenceFdInfoKHR *pImportFenceFdInfo) 539{ 540 tu_stub(); 541 542 return VK_SUCCESS; 543} 544 545VKAPI_ATTR VkResult VKAPI_CALL 546tu_GetFenceFdKHR(VkDevice _device, 547 const VkFenceGetFdInfoKHR *pGetFdInfo, 548 int *pFd) 549{ 550 tu_stub(); 551 552 return VK_SUCCESS; 553} 554 555VKAPI_ATTR VkResult VKAPI_CALL 556tu_CreateFence(VkDevice device, 557 const VkFenceCreateInfo *info, 558 const VkAllocationCallbacks *pAllocator, 559 VkFence *pFence) 560{ 561 return sync_create(device, info->flags & VK_FENCE_CREATE_SIGNALED_BIT, true, 562 pAllocator, (void**) pFence); 563} 564 565VKAPI_ATTR void VKAPI_CALL 566tu_DestroyFence(VkDevice _device, VkFence fence, const VkAllocationCallbacks *pAllocator) 567{ 568 TU_FROM_HANDLE(tu_device, device, _device); 569 TU_FROM_HANDLE(tu_syncobj, sync, fence); 570 571 if (!sync) 572 return; 573 574 vk_object_free(&device->vk, pAllocator, sync); 575} 576 577VKAPI_ATTR VkResult VKAPI_CALL 578tu_WaitForFences(VkDevice _device, 579 uint32_t count, 580 const VkFence *pFences, 581 VkBool32 waitAll, 582 uint64_t timeout) 583{ 584 TU_FROM_HANDLE(tu_device, device, _device); 585 struct tu_syncobj s = sync_merge((const VkSemaphore*) pFences, count, waitAll, false); 586 587 if (!s.timestamp_valid) 588 return VK_SUCCESS; 589 590 int ret = ioctl(device->fd, IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID, 591 &(struct kgsl_device_waittimestamp_ctxtid) { 592 .context_id = device->queues[0]->msm_queue_id, 593 .timestamp = s.timestamp, 594 .timeout = timeout / 1000000, 595 }); 596 if (ret) { 597 assert(errno == ETIME); 598 return VK_TIMEOUT; 599 } 600 601 return VK_SUCCESS; 602} 603 604VKAPI_ATTR VkResult VKAPI_CALL 605tu_ResetFences(VkDevice _device, uint32_t count, const VkFence *pFences) 606{ 607 for (uint32_t i = 0; i < count; i++) { 608 TU_FROM_HANDLE(tu_syncobj, sync, pFences[i]); 609 sync->timestamp_valid = false; 610 } 611 return VK_SUCCESS; 612} 613 614VKAPI_ATTR VkResult VKAPI_CALL 615tu_GetFenceStatus(VkDevice _device, VkFence _fence) 616{ 617 TU_FROM_HANDLE(tu_device, device, _device); 618 TU_FROM_HANDLE(tu_syncobj, sync, _fence); 619 620 if (!sync->timestamp_valid) 621 return VK_NOT_READY; 622 623 int ret = ioctl(device->fd, IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID, 624 &(struct kgsl_device_waittimestamp_ctxtid) { 625 .context_id = device->queues[0]->msm_queue_id, 626 .timestamp = sync->timestamp, 627 .timeout = 0, 628 }); 629 if (ret) { 630 assert(errno == ETIME); 631 return VK_NOT_READY; 632 } 633 634 return VK_SUCCESS; 635} 636 637int 638tu_signal_fences(struct tu_device *device, struct tu_syncobj *fence1, struct tu_syncobj *fence2) 639{ 640 tu_finishme("tu_signal_fences"); 641 return 0; 642} 643 644int 645tu_syncobj_to_fd(struct tu_device *device, struct tu_syncobj *sync) 646{ 647 tu_finishme("tu_syncobj_to_fd"); 648 return -1; 649} 650 651VkResult 652tu_device_submit_deferred_locked(struct tu_device *dev) 653{ 654 tu_finishme("tu_device_submit_deferred_locked"); 655 656 return VK_SUCCESS; 657} 658 659VkResult 660tu_device_wait_u_trace(struct tu_device *dev, struct tu_u_trace_syncobj *syncobj) 661{ 662 tu_finishme("tu_device_wait_u_trace"); 663 return VK_SUCCESS; 664} 665 666int 667tu_drm_get_timestamp(struct tu_physical_device *device, uint64_t *ts) 668{ 669 tu_finishme("tu_drm_get_timestamp"); 670 return 0; 671} 672 673#ifdef ANDROID 674VKAPI_ATTR VkResult VKAPI_CALL 675tu_QueueSignalReleaseImageANDROID(VkQueue _queue, 676 uint32_t waitSemaphoreCount, 677 const VkSemaphore *pWaitSemaphores, 678 VkImage image, 679 int *pNativeFenceFd) 680{ 681 TU_FROM_HANDLE(tu_queue, queue, _queue); 682 if (!pNativeFenceFd) 683 return VK_SUCCESS; 684 685 struct tu_syncobj s = sync_merge(pWaitSemaphores, waitSemaphoreCount, true, true); 686 687 if (!s.timestamp_valid) { 688 *pNativeFenceFd = -1; 689 return VK_SUCCESS; 690 } 691 692 *pNativeFenceFd = timestamp_to_fd(queue, s.timestamp); 693 694 return VK_SUCCESS; 695} 696#endif 697