lib/gpu/src/runtime/vulkan/dmabuf.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 //! Color images the runtime renders into and shares with another process, such as a Wayland
  2 //! compositor, as dma-bufs. Each image has a DRM timeline syncobj that orders the two processes'
  3 //! access to it.
  4 //!
  5 //! On its n-th use (n from 0) image i moves through two points of timeline i:
  6 //! - acquire point 2n+1 signals when the device has finished rendering the frame;
  7 //! - release point 2n+2 is the consumer's to signal once it has stopped reading the image.
  8 //! Use n+1 waits on the host for release point 2n+2 before recording anything that touches the
  9 //! image. Each image has its own timeline, so points on a timeline are always added in increasing
 10 //! order, however the consumer orders its releases across images.
 11 //!
 12 //! Ownership moves between the runtime's queue family and `VK_QUEUE_FAMILY_FOREIGN_EXT`. A frame
 13 //! acquires the image from the foreign family in the general layout, renders to it in
 14 //! `COLOR_ATTACHMENT_OPTIMAL`, and releases it back in the general layout. The first use has
 15 //! nothing to acquire and starts from `UNDEFINED`.
 16 //!
 17 //! The device's completion reaches the timeline through one binary semaphore. Each frame's
 18 //! submission signals it, then it is exported as a sync_file, which also unsignals it (copy
 19 //! transference). The sync_file is imported at the acquire point with one ioctl. Every Linux Vulkan
 20 //! driver exports SYNC_FD. Importing a syncobj into Vulkan as a timeline semaphore would skip the
 21 //! sync_file, but drivers need not support that import.
 22 
 23 const std = @import("std");
 24 const sys = @import("sys");
 25 
 26 const driver_mod = sys.vulkan;
 27 const drm = sys.drm;
 28 const raster_mod = @import("raster.zig");
 29 const runtime_mod = @import("runtime.zig");
 30 
 31 pub const Error = driver_mod.Error || drm.Error || error{
 32     /// The device did not enable dma-buf export or cannot name its render node.
 33     NoDmabufDevice,
 34     /// The device supports no modifier of the format that the consumer lists.
 35     NoSharedModifier,
 36 };
 37 
 38 const Raster = raster_mod.Raster;
 39 const Runtime = runtime_mod.Runtime;
 40 const VkCommandBuffer = driver_mod.VkCommandBuffer;
 41 const VkFormat = driver_mod.VkFormat;
 42 const VkImage = driver_mod.VkImage;
 43 const VkSemaphore = driver_mod.VkSemaphore;
 44 
 45 /// Images in one target. Four covers a consumer that holds one image on screen, one queued, and
 46 /// one being released while the renderer draws the fourth.
 47 pub const max_images: u32 = 4;
 48 
 49 /// Memory planes one image may have. A modifier with compression metadata uses more than one.
 50 pub const max_planes: u32 = 4;
 51 
 52 /// Modifiers of one format, as a device lists them or a consumer offers them.
 53 pub const max_modifiers: u32 = 64;
 54 
 55 /// How long `destroy` waits for the consumer to release each image before freeing it anyway.
 56 const release_grace_ns: i64 = std.time.ns_per_s;
 57 
 58 const color_layout = driver_mod.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 59 const shared_layout = driver_mod.VK_IMAGE_LAYOUT_GENERAL;
 60 const all_access = driver_mod.VK_ACCESS_MEMORY_READ_BIT | driver_mod.VK_ACCESS_MEMORY_WRITE_BIT;
 61 const dma_buf = driver_mod.VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
 62 const sync_fd = driver_mod.VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
 63 
 64 const plane_aspects = [max_planes]driver_mod.VkImageAspectFlags{
 65     driver_mod.VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT,
 66     driver_mod.VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT,
 67     driver_mod.VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT,
 68     driver_mod.VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT,
 69 };
 70 
 71 pub const Plane = struct {
 72     offset: u32,
 73     stride: u32,
 74 };
 75 
 76 /// What a consumer needs to import one image: the dma-buf, the modifier the driver chose, and
 77 /// where each memory plane sits in the dma-buf.
 78 pub const Dmabuf = struct {
 79     /// Owned by the target and closed when it is destroyed. A consumer that takes ownership of a
 80     /// descriptor it is handed needs its own duplicate.
 81     fd: i32,
 82     modifier: u64,
 83     planes: [max_planes]Plane,
 84     plane_count: u32,
 85 };
 86 
 87 pub const Desc = struct {
 88     width: u32,
 89     height: u32,
 90     format: VkFormat,
 91     /// Modifiers of `format` the consumer can import.
 92     modifiers: []const u64,
 93     image_count: u32,
 94     /// The device the consumer wants buffers from, such as a compositor's main device.
 95     device: drm.DeviceNumber,
 96 };
 97 
 98 /// One image's turn: which image, and the points that bound it.
 99 pub const Frame = struct {
100     index: u32,
101     acquire_point: u64,
102     release_point: u64,
103 };
104 
105 const Slot = struct {
106     image: VkImage,
107     dmabuf: Dmabuf,
108     timeline: drm.Syncobj,
109     /// The timeline exported for the consumer, owned by the target.
110     timeline_fd: i32,
111     /// Frames rendered into the image so far.
112     uses: u64,
113 };
114 
115 pub const Target = struct {
116     runtime: *Runtime,
117     node: drm.RenderNode,
118     semaphore: VkSemaphore,
119     slots: [max_images]Slot,
120     count: u32,
121     /// The image the next frame renders into.
122     next: u32,
123     /// The frame begun and not yet submitted.
124     open: ?Frame,
125     /// A timeline for `bridgeScratch`, made on its first call.
126     scratch: ?drm.Syncobj = null,
127     scratch_point: u64 = 0,
128 
129     /// Makes `desc.image_count` images from the modifiers the device and the consumer share, each
130     /// with its view in `images`, its dma-buf and its timeline. The caller owns `images` and
131     /// destroys them after the target.
132     pub fn create(raster: *Raster, desc: Desc, images: *[max_images]raster_mod.Image) Error!Target {
133         if (desc.image_count == 0 or desc.image_count > max_images) return error.InitializationFailed;
134         if (desc.width == 0 or desc.height == 0) return error.InitializationFailed;
135         if (desc.width > raster.max_image_extent or desc.height > raster.max_image_extent) return error.InitializationFailed;
136         const rt = raster.runtime;
137         const device = rt.device.dmabufRenderNode() orelse return error.NoDmabufDevice;
138         if (!std.meta.eql(device, desc.device)) return error.DeviceMismatch;
139         var shared_storage: [max_modifiers]u64 = undefined;
140         const modifiers = try sharedModifiers(rt, desc.format, desc.modifiers, &shared_storage);
141 
142         const node = try drm.RenderNode.open(device);
143         errdefer node.close();
144         const semaphore = try exportableSemaphore(rt);
145         errdefer rt._driver.vkDestroySemaphore(rt._logical_device, semaphore, null);
146 
147         var target = Target{
148             .runtime = rt,
149             .node = node,
150             .semaphore = semaphore,
151             .slots = undefined,
152             .count = 0,
153             .next = 0,
154             .open = null,
155         };
156         errdefer while (target.count > 0) {
157             target.count -= 1;
158             target.closeSlot(target.count);
159             images[target.count].destroy(raster);
160         };
161         while (target.count < desc.image_count) {
162             const index = target.count;
163             images[index] = try createImage(raster, desc, modifiers);
164             errdefer images[index].destroy(raster);
165             target.slots[index] = try target.openSlot(&images[index]);
166             target.count += 1;
167         }
168         return target;
169     }
170 
171     /// Waits for the consumer to release every image, up to a grace period, then frees the
172     /// target's descriptors, syncobjs and semaphore. The caller then destroys the images.
173     pub fn destroy(self: *Target, raster: *Raster) void {
174         std.debug.assert(self.open == null);
175         raster.idle();
176         const deadline = drm.monotonicNow() + release_grace_ns;
177         for (self.slots[0..self.count], 0..) |slot, index| {
178             if (slot.uses != 0) self.node.waitPoint(slot.timeline, 2 * slot.uses, deadline) catch |err| {
179                 std.log.scoped(.vulkan_dmabuf).warn("image {d} not released before destroy: {s}", .{ index, @errorName(err) });
180             };
181             self.closeSlot(@intCast(index));
182         }
183         if (self.scratch) |scratch| self.node.destroySyncobj(scratch);
184         self.runtime._driver.vkDestroySemaphore(self.runtime._logical_device, self.semaphore, null);
185         self.node.close();
186         self.* = undefined;
187     }
188 
189     pub fn dmabuf(self: *const Target, index: u32) Dmabuf {
190         std.debug.assert(index < self.count);
191         return self.slots[index].dmabuf;
192     }
193 
194     /// The descriptor of image `index`'s timeline, owned by the target.
195     pub fn timelineFd(self: *const Target, index: u32) i32 {
196         std.debug.assert(index < self.count);
197         return self.slots[index].timeline_fd;
198     }
199 
200     /// The highest point of image `index`'s timeline that has signalled.
201     pub fn signalledPoint(self: *const Target, index: u32) Error!u64 {
202         std.debug.assert(index < self.count);
203         return self.node.signalledPoint(self.slots[index].timeline);
204     }
205 
206     /// Picks the next image and waits, until `deadline_ns` on `CLOCK_MONOTONIC`, for the consumer
207     /// to release it from its previous frame.
208     pub fn begin(self: *Target, deadline_ns: i64) Error!Frame {
209         std.debug.assert(self.open == null);
210         const index = self.next;
211         const slot = &self.slots[index];
212         if (slot.uses != 0) try self.node.waitPoint(slot.timeline, 2 * slot.uses, deadline_ns);
213         const frame = Frame{ .index = index, .acquire_point = 2 * slot.uses + 1, .release_point = 2 * slot.uses + 2 };
214         self.open = frame;
215         return frame;
216     }
217 
218     /// Records the barrier that takes the frame's image from the consumer into the attachment
219     /// layout a pass renders in.
220     pub fn recordAcquire(self: *const Target, cb: VkCommandBuffer, frame: Frame) void {
221         const slot = &self.slots[frame.index];
222         const first = slot.uses == 0;
223         const handoff = self.ownershipBarrier(slot.image, .{
224             .src_access = 0,
225             .dst_access = all_access,
226             .old = if (first) driver_mod.VK_IMAGE_LAYOUT_UNDEFINED else shared_layout,
227             .new = color_layout,
228             .src_family = if (first) driver_mod.VK_QUEUE_FAMILY_IGNORED else driver_mod.VK_QUEUE_FAMILY_FOREIGN_EXT,
229             .dst_family = if (first) driver_mod.VK_QUEUE_FAMILY_IGNORED else self.runtime.device.queueFamilyIndex(),
230         });
231         raster_mod.recordBarrier(&self.runtime._driver, cb, &.{handoff});
232     }
233 
234     /// Records the barrier that hands the frame's image to the consumer once the pass has written it.
235     pub fn recordRelease(self: *const Target, cb: VkCommandBuffer, frame: Frame) void {
236         const handoff = self.ownershipBarrier(self.slots[frame.index].image, .{
237             .src_access = driver_mod.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
238             .dst_access = 0,
239             .old = color_layout,
240             .new = shared_layout,
241             .src_family = self.runtime.device.queueFamilyIndex(),
242             .dst_family = driver_mod.VK_QUEUE_FAMILY_FOREIGN_EXT,
243         });
244         raster_mod.recordBarrier(&self.runtime._driver, cb, &.{handoff});
245     }
246 
247     /// Attaches the fence of the submission that signalled `semaphore` to the frame's acquire
248     /// point and moves to the next image. The frame's submission must already be queued.
249     pub fn finish(self: *Target, frame: Frame) Error!void {
250         std.debug.assert(std.meta.eql(self.open, frame));
251         const slot = &self.slots[frame.index];
252         try self.bridge(slot.timeline, frame.acquire_point);
253         slot.uses += 1;
254         self.next = (self.next + 1) % self.count;
255         self.open = null;
256     }
257 
258     /// Signals a finished frame's release point from the host, for a frame the consumer never
259     /// received. The point still follows the frame's acquire point, since a timeline point
260     /// signals only once every earlier point has.
261     pub fn reclaim(self: *Target, frame: Frame) Error!void {
262         std.debug.assert(frame.index < self.count);
263         std.debug.assert(frame.release_point == 2 * self.slots[frame.index].uses);
264         try self.node.signalPoint(self.slots[frame.index].timeline, frame.release_point);
265     }
266 
267     /// Exports the semaphore that a submission just signalled as a sync_file, and attaches it to
268     /// the next point of a scratch timeline. The same work `finish` does, on a timeline no
269     /// consumer reads, so a caller can time it apart from any frame.
270     pub fn bridgeScratch(self: *Target) Error!void {
271         const scratch = self.scratch orelse blk: {
272             const created = try self.node.createSyncobj();
273             self.scratch = created;
274             break :blk created;
275         };
276         self.scratch_point += 1;
277         try self.bridge(scratch, self.scratch_point);
278     }
279 
280     /// Gives up a begun frame whose submission never queued. The image keeps its previous state.
281     pub fn abandon(self: *Target, frame: Frame) void {
282         std.debug.assert(std.meta.eql(self.open, frame));
283         self.open = null;
284     }
285 
286     /// Moves the fence of the semaphore's last signal onto `point` of `timeline`. A fence that had
287     /// already signalled when exported comes back as -1, and the point is signalled from the host
288     /// instead.
289     fn bridge(self: *Target, timeline: drm.Syncobj, point: u64) Error!void {
290         const rt = self.runtime;
291         const drv = &rt._driver;
292         const get_fd = drv.vkGetSemaphoreFdKHR orelse return error.NoDmabufDevice;
293         var fence: c_int = -1;
294         const info = driver_mod.VkSemaphoreGetFdInfoKHR{ .semaphore = self.semaphore, .handleType = sync_fd };
295         try drv.fromResult(get_fd(rt._logical_device, &info, &fence));
296         if (fence < 0) return self.node.signalPoint(timeline, point);
297         defer sys.fd.close(fence);
298         try self.node.importSyncFile(timeline, point, fence);
299     }
300 
301     const BarrierDesc = struct {
302         src_access: driver_mod.VkAccessFlags,
303         dst_access: driver_mod.VkAccessFlags,
304         old: driver_mod.VkImageLayout,
305         new: driver_mod.VkImageLayout,
306         src_family: u32,
307         dst_family: u32,
308     };
309 
310     fn ownershipBarrier(_: *const Target, image: VkImage, desc: BarrierDesc) driver_mod.VkImageMemoryBarrier {
311         return .{
312             .sType = driver_mod.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
313             .pNext = null,
314             .srcAccessMask = desc.src_access,
315             .dstAccessMask = desc.dst_access,
316             .oldLayout = desc.old,
317             .newLayout = desc.new,
318             .srcQueueFamilyIndex = desc.src_family,
319             .dstQueueFamilyIndex = desc.dst_family,
320             .image = image,
321             .subresourceRange = raster_mod.subresourceRange(driver_mod.VK_IMAGE_ASPECT_COLOR_BIT),
322         };
323     }
324 
325     /// Exports `image`'s memory and plane layout and gives it a fresh timeline.
326     fn openSlot(self: *const Target, image: *const raster_mod.Image) Error!Slot {
327         const rt = self.runtime;
328         const drv = &rt._driver;
329         const device = rt._logical_device;
330         const memory = image.backing.dedicated;
331         const get_modifier = drv.vkGetImageDrmFormatModifierPropertiesEXT orelse return error.NoDmabufDevice;
332         const get_memory_fd = drv.vkGetMemoryFdKHR orelse return error.NoDmabufDevice;
333 
334         var modifier_properties = driver_mod.VkImageDrmFormatModifierPropertiesEXT{};
335         try drv.fromResult(get_modifier(device, image.image, &modifier_properties));
336         const plane_count = try planeCount(rt, image.format, modifier_properties.drmFormatModifier);
337         var planes: [max_planes]Plane = undefined;
338         for (plane_aspects[0..plane_count], planes[0..plane_count]) |aspect, *plane| {
339             var layout = driver_mod.VkSubresourceLayout{};
340             drv.vkGetImageSubresourceLayout(device, image.image, &.{ .aspectMask = aspect }, &layout);
341             plane.* = .{
342                 .offset = std.math.cast(u32, layout.offset) orelse return error.InitializationFailed,
343                 .stride = std.math.cast(u32, layout.rowPitch) orelse return error.InitializationFailed,
344             };
345         }
346 
347         var fd: c_int = -1;
348         const fd_info = driver_mod.VkMemoryGetFdInfoKHR{ .memory = memory, .handleType = dma_buf };
349         try drv.fromResult(get_memory_fd(device, &fd_info, &fd));
350         errdefer sys.fd.close(fd);
351         const timeline = try self.node.createSyncobj();
352         errdefer self.node.destroySyncobj(timeline);
353         const timeline_fd = try self.node.exportSyncobj(timeline);
354         return .{
355             .image = image.image,
356             .dmabuf = .{
357                 .fd = fd,
358                 .modifier = modifier_properties.drmFormatModifier,
359                 .planes = planes,
360                 .plane_count = plane_count,
361             },
362             .timeline = timeline,
363             .timeline_fd = timeline_fd,
364             .uses = 0,
365         };
366     }
367 
368     fn closeSlot(self: *const Target, index: u32) void {
369         const slot = self.slots[index];
370         sys.fd.close(slot.timeline_fd);
371         self.node.destroySyncobj(slot.timeline);
372         sys.fd.close(slot.dmabuf.fd);
373     }
374 };
375 
376 /// The modifiers in `offered` that the device can render `format` to, in the consumer's order.
377 fn sharedModifiers(rt: *Runtime, format: VkFormat, offered: []const u64, storage: *[max_modifiers]u64) Error![]const u64 {
378     var listed_storage: [max_modifiers]driver_mod.VkDrmFormatModifierPropertiesEXT = undefined;
379     const listed = listModifiers(rt, format, &listed_storage);
380     var count: usize = 0;
381     for (offered) |modifier| {
382         if (count == max_modifiers) break;
383         for (listed) |entry| {
384             if (entry.drmFormatModifier != modifier) continue;
385             if (entry.drmFormatModifierTilingFeatures & driver_mod.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT == 0) break;
386             if (entry.drmFormatModifierPlaneCount > max_planes) break;
387             storage[count] = modifier;
388             count += 1;
389             break;
390         }
391     }
392     if (count == 0) return error.NoSharedModifier;
393     return storage[0..count];
394 }
395 
396 fn listModifiers(
397     rt: *Runtime,
398     format: VkFormat,
399     storage: *[max_modifiers]driver_mod.VkDrmFormatModifierPropertiesEXT,
400 ) []const driver_mod.VkDrmFormatModifierPropertiesEXT {
401     var list = driver_mod.VkDrmFormatModifierPropertiesListEXT{
402         .drmFormatModifierCount = max_modifiers,
403         .pDrmFormatModifierProperties = storage,
404     };
405     var properties = driver_mod.VkFormatProperties2{ .pNext = &list };
406     rt._driver.vkGetPhysicalDeviceFormatProperties2(rt._physical_device, format, &properties);
407     return storage[0..@min(list.drmFormatModifierCount, max_modifiers)];
408 }
409 
410 fn planeCount(rt: *Runtime, format: VkFormat, modifier: u64) Error!u32 {
411     var storage: [max_modifiers]driver_mod.VkDrmFormatModifierPropertiesEXT = undefined;
412     for (listModifiers(rt, format, &storage)) |entry| {
413         if (entry.drmFormatModifier == modifier) {
414             if (entry.drmFormatModifierPlaneCount == 0 or entry.drmFormatModifierPlaneCount > max_planes) return error.InitializationFailed;
415             return entry.drmFormatModifierPlaneCount;
416         }
417     }
418     return error.NoSharedModifier;
419 }
420 
421 fn exportableSemaphore(rt: *Runtime) Error!VkSemaphore {
422     const export_info = driver_mod.VkExportSemaphoreCreateInfo{ .handleTypes = sync_fd };
423     const info = driver_mod.VkSemaphoreCreateInfo{
424         .sType = driver_mod.VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
425         .pNext = &export_info,
426         .flags = 0,
427     };
428     var semaphore: VkSemaphore = null;
429     try rt._driver.fromResult(rt._driver.vkCreateSemaphore(rt._logical_device, &info, null, &semaphore));
430     return semaphore;
431 }
432 
433 /// A color attachment with a modifier the driver picks from `modifiers`, bound to a dedicated
434 /// allocation it can export, and its view. It stays in `UNDEFINED` until its first frame.
435 fn createImage(raster: *Raster, desc: Desc, modifiers: []const u64) Error!raster_mod.Image {
436     const rt = raster.runtime;
437     const drv = &rt._driver;
438     const device = rt._logical_device;
439     const modifier_list = driver_mod.VkImageDrmFormatModifierListCreateInfoEXT{
440         .drmFormatModifierCount = @intCast(modifiers.len),
441         .pDrmFormatModifiers = modifiers.ptr,
442     };
443     const external = driver_mod.VkExternalMemoryImageCreateInfo{ .pNext = &modifier_list, .handleTypes = dma_buf };
444     const ci = driver_mod.VkImageCreateInfo{
445         .sType = driver_mod.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
446         .pNext = &external,
447         .flags = 0,
448         .imageType = driver_mod.VK_IMAGE_TYPE_2D,
449         .format = desc.format,
450         .extent = .{ .width = desc.width, .height = desc.height, .depth = 1 },
451         .mipLevels = 1,
452         .arrayLayers = 1,
453         .samples = driver_mod.VK_SAMPLE_COUNT_1_BIT,
454         .tiling = driver_mod.VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
455         .usage = driver_mod.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
456         .sharingMode = driver_mod.VK_SHARING_MODE_EXCLUSIVE,
457         .queueFamilyIndexCount = 0,
458         .pQueueFamilyIndices = null,
459         .initialLayout = driver_mod.VK_IMAGE_LAYOUT_UNDEFINED,
460     };
461     var image: VkImage = 0;
462     try drv.fromResult(drv.vkCreateImage(device, &ci, null, &image));
463     errdefer drv.vkDestroyImage(device, image, null);
464 
465     var requirements: driver_mod.VkMemoryRequirements = undefined;
466     drv.vkGetImageMemoryRequirements(device, image, &requirements);
467     const type_index = rt._memory.typeFor(requirements, .device) orelse return error.OutOfDeviceMemory;
468     const dedicated = driver_mod.VkMemoryDedicatedAllocateInfo{ .image = image };
469     const export_info = driver_mod.VkExportMemoryAllocateInfo{ .pNext = &dedicated, .handleTypes = dma_buf };
470     const allocate_info = driver_mod.VkMemoryAllocateInfo{
471         .sType = driver_mod.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
472         .pNext = &export_info,
473         .allocationSize = requirements.size,
474         .memoryTypeIndex = type_index,
475     };
476     var memory: driver_mod.VkDeviceMemory = null;
477     try drv.fromResult(drv.vkAllocateMemory(device, &allocate_info, null, &memory));
478     errdefer drv.vkFreeMemory(device, memory, null);
479     try drv.fromResult(drv.vkBindImageMemory(device, image, memory, 0));
480 
481     const view_ci = driver_mod.VkImageViewCreateInfo{
482         .sType = driver_mod.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
483         .pNext = null,
484         .flags = 0,
485         .image = image,
486         .viewType = driver_mod.VK_IMAGE_VIEW_TYPE_2D,
487         .format = desc.format,
488         .components = .{},
489         .subresourceRange = raster_mod.subresourceRange(driver_mod.VK_IMAGE_ASPECT_COLOR_BIT),
490     };
491     var view: driver_mod.VkImageView = 0;
492     try drv.fromResult(drv.vkCreateImageView(device, &view_ci, null, &view));
493     return .{
494         .image = image,
495         .view = view,
496         .backing = .{ .dedicated = memory },
497         .width = desc.width,
498         .height = desc.height,
499         .format = desc.format,
500         .aspect = driver_mod.VK_IMAGE_ASPECT_COLOR_BIT,
501         .texel_bytes = 4,
502         .home = color_layout,
503     };
504 }