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