summaryrefslogtreecommitdiff
path: root/src/vulkan/runtime/vk_device.h
blob: d1dc154ac8c9a3c0edcd2fe6b2d7f80b0454f55c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/*
 * Copyright © 2020 Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
#ifndef VK_DEVICE_H
#define VK_DEVICE_H

#include "vk_dispatch_table.h"
#include "vk_extensions.h"
#include "vk_object.h"

#include "util/list.h"
#include "util/u_atomic.h"

#ifdef __cplusplus
extern "C" {
#endif

struct vk_sync;

enum vk_queue_submit_mode {
   /** Submits happen immediately
    *
    * `vkQueueSubmit()` and `vkQueueBindSparse()` call
    * `vk_queue::driver_submit` directly for all submits and the last call to
    * `vk_queue::driver_submit` will have completed by the time
    * `vkQueueSubmit()` or `vkQueueBindSparse()` return.
    */
   VK_QUEUE_SUBMIT_MODE_IMMEDIATE,

   /** Submits may be deferred until a future `vk_queue_flush()`
    *
    * Submits are added to the queue and `vk_queue_flush()` is called.
    * However, any submits with unsatisfied dependencies will be left on the
    * queue until a future `vk_queue_flush()` call.  This is used for
    * implementing emulated timeline semaphores without threading.
    */
   VK_QUEUE_SUBMIT_MODE_DEFERRED,

   /** Submits will be added to the queue and handled later by a thread
    *
    * This places additional requirements on the vk_sync types used by the
    * driver:
    *
    *    1. All `vk_sync` types which support `VK_SYNC_FEATURE_GPU_WAIT` also
    *       support `VK_SYNC_FEATURE_WAIT_PENDING` so that the threads can
    *       sort out when a given submit has all its dependencies resolved.
    *
    *    2. All binary `vk_sync` types which support `VK_SYNC_FEATURE_GPU_WAIT`
    *       also support `VK_SYNC_FEATURE_CPU_RESET` so we can reset
    *       semaphores after waiting on them.
    *
    *    3. All vk_sync types used as permanent payloads of semaphores support
    *       `vk_sync_type::move` so that it can move the pending signal into a
    *       temporary vk_sync and reset the semaphore.
    *
    * This is requied for shared timeline semaphores where we need to handle
    * wait-before-signal by threading in the driver if we ever see an
    * unresolve dependency.
    */
   VK_QUEUE_SUBMIT_MODE_THREADED,

   /** Threaded but only if we need it to resolve dependencies
    *
    * This imposes all the same requirements on `vk_sync` types as
    * `VK_QUEUE_SUBMIT_MODE_THREADED`.
    */
   VK_QUEUE_SUBMIT_MODE_THREADED_ON_DEMAND,
};

/** Base struct for VkDevice */
struct vk_device {
   struct vk_object_base base;

   /** Allocator used to create this device
    *
    * This is used as a fall-back for when a NULL pAllocator is passed into a
    * device-level create function such as vkCreateImage().
    */
   VkAllocationCallbacks alloc;

   /** Pointer to the physical device */
   struct vk_physical_device *physical;

   /** Table of enabled extensions */
   struct vk_device_extension_table enabled_extensions;

   /** Device-level dispatch table */
   struct vk_device_dispatch_table dispatch_table;

   /** Command dispatch table
    *
    * This is used for emulated secondary command buffer support.  To use
    * emulated (trace/replay) secondary command buffers:
    *
    *  1. Provide your "real" command buffer dispatch table here.  Because
    *     this doesn't get populated by vk_device_init(), the driver will have
    *     to add the vk_common entrypoints to this table itself.
    *
    *  2. Add vk_enqueue_unless_primary_device_entrypoint_table to your device
    *     level dispatch table.
    */
   const struct vk_device_dispatch_table *command_dispatch_table;

   /* For VK_EXT_private_data */
   uint32_t private_data_next_index;

   struct list_head queues;

   struct {
      int lost;
      bool reported;
   } _lost;

   /** Checks the status of this device
    *
    * This is expected to return either VK_SUCCESS or VK_ERROR_DEVICE_LOST.
    * It is called before vk_queue::driver_submit and after every non-trivial
    * wait operation to ensure the device is still around.  This gives the
    * driver a hook to ask the kernel if its device is still valid.  If the
    * kernel says the device has been lost, it MUST call vk_device_set_lost().
    *
    * This function may be called from any thread at any time.
    */
   VkResult (*check_status)(struct vk_device *device);

   /** Creates a vk_sync that wraps a memory object
    *
    * This is always a one-shot object so it need not track any additional
    * state.  Since it's intended for synchronizing between processes using
    * implicit synchronization mechanisms, no such tracking would be valid
    * anyway.
    *
    * If `signal_memory` is set, the resulting vk_sync will be used to signal
    * the memory object from a queue via vk_queue_submit::signals.  The common
    * code guarantees that, by the time vkQueueSubmit() returns, the signal
    * operation has been submitted to the kernel via the driver's
    * vk_queue::driver_submit hook.  This means that any vkQueueSubmit() call
    * which needs implicit synchronization may block.
    *
    * If `signal_memory` is not set, it can be assumed that memory object
    * already has a signal operation pending from some other process and we
    * need only wait on it.
    */
   VkResult (*create_sync_for_memory)(struct vk_device *device,
                                      VkDeviceMemory memory,
                                      bool signal_memory,
                                      struct vk_sync **sync_out);

   /** Increments the reference count on a pipeline layout
    *
    * This is required for vk_enqueue_CmdBindDescriptorSets() to avoid
    * use-after-free problems with pipeline layouts.  If you're not using
    * the command queue, you can ignore this.
    */
   void (*ref_pipeline_layout)(struct vk_device *device,
                               VkPipelineLayout layout);

   /** Decrements the reference count on a pipeline layout
    *
    * See ref_pipeline_layout above.
    */
   void (*unref_pipeline_layout)(struct vk_device *device,
                                 VkPipelineLayout layout);

   /* Set by vk_device_set_drm_fd() */
   int drm_fd;

   /** An enum describing how timeline semaphores work */
   enum vk_device_timeline_mode {
      /** Timeline semaphores are not supported */
      VK_DEVICE_TIMELINE_MODE_NONE,

      /** Timeline semaphores are emulated with vk_timeline
       *
       * In this mode, timeline semaphores are emulated using vk_timeline
       * which is a collection of binary semaphores, one per time point.
       * These timeline semaphores cannot be shared because the data structure
       * exists entirely in userspace.  These timelines are virtually
       * invisible to the driver; all it sees are the binary vk_syncs, one per
       * time point.
       *
       * To handle wait-before-signal, we place all vk_queue_submits in the
       * queue's submit list in vkQueueSubmit() and call vk_device_flush() at
       * key points such as the end of vkQueueSubmit() and vkSemaphoreSignal().
       * This ensures that, as soon as a given submit's dependencies are fully
       * resolvable, it gets submitted to the driver.
       */
      VK_DEVICE_TIMELINE_MODE_EMULATED,

      /** Timeline semaphores are a kernel-assisted emulation
       *
       * In this mode, timeline semaphores are still technically an emulation
       * in the sense that they don't support wait-before-signal natively.
       * Instead, all GPU-waitable objects support a CPU wait-for-pending
       * operation which lets the userspace driver wait until a given event
       * on the (possibly shared) vk_sync is pending.  The event is "pending"
       * if a job has been submitted to the kernel (possibly from a different
       * process) which will signal it.  In vkQueueSubit, we use this wait
       * mode to detect waits which are not yet pending and, the first time we
       * do, spawn a thread to manage the queue.  That thread waits for each
       * submit's waits to all be pending before submitting to the driver
       * queue.
       *
       * We have to be a bit more careful about a few things in this mode.
       * In particular, we can never assume that any given wait operation is
       * pending.  For instance, when we go to export a sync file from a
       * binary semaphore, we need to first wait for it to be pending.  The
       * spec guarantees that the vast majority of these waits return almost
       * immediately, but we do need to insert them for correctness.
       */
      VK_DEVICE_TIMELINE_MODE_ASSISTED,

      /** Timeline semaphores are 100% native
       *
       * In this mode, wait-before-signal is natively supported by the
       * underlying timeline implementation.  We can submit-and-forget and
       * assume that dependencies will get resolved for us by the kernel.
       * Currently, this isn't supported by any Linux primitives.
       */
      VK_DEVICE_TIMELINE_MODE_NATIVE,
   } timeline_mode;

   /** Per-device submit mode
    *
    * This represents the device-wide submit strategy which may be different
    * from the per-queue submit mode.  See vk_queue.submit.mode for more
    * details.
    */
   enum vk_queue_submit_mode submit_mode;

#ifdef ANDROID
   mtx_t swapchain_private_mtx;
   struct hash_table *swapchain_private;
#endif
};

VK_DEFINE_HANDLE_CASTS(vk_device, base, VkDevice,
                       VK_OBJECT_TYPE_DEVICE)

VkResult MUST_CHECK
vk_device_init(struct vk_device *device,
               struct vk_physical_device *physical_device,
               const struct vk_device_dispatch_table *dispatch_table,
               const VkDeviceCreateInfo *pCreateInfo,
               const VkAllocationCallbacks *alloc);

static inline void
vk_device_set_drm_fd(struct vk_device *device, int drm_fd)
{
   device->drm_fd = drm_fd;
}

void
vk_device_finish(struct vk_device *device);

/** Enables threaded submit on this device
 *
 * This doesn't ensure that threaded submit will be used.  It just disables
 * the deferred submit option for emulated timeline semaphores and forces them
 * to always use the threaded path.  It also does some checks that the vk_sync
 * types used by the driver work for threaded submit.
 *
 * This must be called before any queues are created.
 */
void vk_device_enable_threaded_submit(struct vk_device *device);

static inline bool
vk_device_supports_threaded_submit(const struct vk_device *device)
{
   return device->submit_mode == VK_QUEUE_SUBMIT_MODE_THREADED ||
          device->submit_mode == VK_QUEUE_SUBMIT_MODE_THREADED_ON_DEMAND;
}

VkResult vk_device_flush(struct vk_device *device);

VkResult PRINTFLIKE(4, 5)
_vk_device_set_lost(struct vk_device *device,
                    const char *file, int line,
                    const char *msg, ...);

#define vk_device_set_lost(device, ...) \
   _vk_device_set_lost(device, __FILE__, __LINE__, __VA_ARGS__)

void _vk_device_report_lost(struct vk_device *device);

static inline bool
vk_device_is_lost_no_report(struct vk_device *device)
{
   return p_atomic_read(&device->_lost.lost) > 0;
}

static inline bool
vk_device_is_lost(struct vk_device *device)
{
   int lost = vk_device_is_lost_no_report(device);
   if (unlikely(lost && !device->_lost.reported))
      _vk_device_report_lost(device);
   return lost;
}

static inline VkResult
vk_device_check_status(struct vk_device *device)
{
   if (vk_device_is_lost(device))
      return VK_ERROR_DEVICE_LOST;

   if (!device->check_status)
      return VK_SUCCESS;

   VkResult result = device->check_status(device);

   assert(result == VK_SUCCESS || result == VK_ERROR_DEVICE_LOST);
   if (result == VK_ERROR_DEVICE_LOST)
      assert(vk_device_is_lost_no_report(device));

   return result;
}

PFN_vkVoidFunction
vk_device_get_proc_addr(const struct vk_device *device,
                        const char *name);

bool vk_get_physical_device_core_1_1_feature_ext(struct VkBaseOutStructure *ext,
                                                 const VkPhysicalDeviceVulkan11Features *core);
bool vk_get_physical_device_core_1_2_feature_ext(struct VkBaseOutStructure *ext,
                                                 const VkPhysicalDeviceVulkan12Features *core);
bool vk_get_physical_device_core_1_3_feature_ext(struct VkBaseOutStructure *ext,
                                                 const VkPhysicalDeviceVulkan13Features *core);

bool vk_get_physical_device_core_1_1_property_ext(struct VkBaseOutStructure *ext,
                                                     const VkPhysicalDeviceVulkan11Properties *core);
bool vk_get_physical_device_core_1_2_property_ext(struct VkBaseOutStructure *ext,
                                                     const VkPhysicalDeviceVulkan12Properties *core);
bool vk_get_physical_device_core_1_3_property_ext(struct VkBaseOutStructure *ext,
                                                     const VkPhysicalDeviceVulkan13Properties *core);

#ifdef __cplusplus
}
#endif

#endif /* VK_DEVICE_H */