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 }