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

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 //! Graphics on the runtime's one queue: images, graphics pipelines built for dynamic rendering,
   2 //! descriptor sets written once, and passes recorded into command buffers.
   3 //!
   4 //! Every image rests in one home layout chosen from its usage. A pass or a transfer moves an image
   5 //! out of its home and back inside the same command buffer, so a command buffer recorded once
   6 //! stays valid for every later submission. Each pass opens with a barrier over all earlier work on
   7 //! the queue, which makes kernel, transfer and earlier pass writes visible to the pass's vertex,
   8 //! index, uniform, sampler and attachment reads.
   9 
  10 const std = @import("std");
  11 
  12 const driver_mod = @import("sys").vulkan;
  13 const memory_mod = @import("memory.zig");
  14 const runtime_mod = @import("runtime.zig");
  15 
  16 pub const Error = driver_mod.Error;
  17 
  18 const Driver = driver_mod.Driver;
  19 const Runtime = runtime_mod.Runtime;
  20 const Stream = runtime_mod.Stream;
  21 const Event = runtime_mod.Event;
  22 
  23 const VkBuffer = driver_mod.VkBuffer;
  24 const VkCommandBuffer = driver_mod.VkCommandBuffer;
  25 const VkCommandPool = driver_mod.VkCommandPool;
  26 const VkDescriptorPool = driver_mod.VkDescriptorPool;
  27 const VkDescriptorSet = driver_mod.VkDescriptorSet;
  28 const VkDescriptorSetLayout = driver_mod.VkDescriptorSetLayout;
  29 const VkDescriptorSetLayoutBinding = driver_mod.VkDescriptorSetLayoutBinding;
  30 const VkFormat = driver_mod.VkFormat;
  31 const VkImage = driver_mod.VkImage;
  32 const VkImageAspectFlags = driver_mod.VkImageAspectFlags;
  33 const VkImageLayout = driver_mod.VkImageLayout;
  34 const VkImageUsageFlags = driver_mod.VkImageUsageFlags;
  35 const VkImageView = driver_mod.VkImageView;
  36 const VkPipeline = driver_mod.VkPipeline;
  37 const VkPipelineLayout = driver_mod.VkPipelineLayout;
  38 const VkSampler = driver_mod.VkSampler;
  39 
  40 /// Waits one submission may name besides its stream's own timeline.
  41 pub const max_wait_events: usize = 32;
  42 
  43 /// Resources one binding set holds.
  44 pub const max_descriptors: u32 = 16;
  45 
  46 /// Vertex buffers one pipeline reads.
  47 pub const max_vertex_buffers: u32 = 8;
  48 
  49 /// Vertex attributes one pipeline reads.
  50 pub const max_vertex_attributes: u32 = 16;
  51 
  52 /// Push-constant bytes a pipeline may declare: the 128 Vulkan guarantees on every device.
  53 pub const max_push_constant_bytes: u32 = 128;
  54 
  55 /// Sets one descriptor pool holds. Each pool reserves `max_descriptors` of every descriptor type
  56 /// per set, so a pool with a free set always has room for its descriptors.
  57 const sets_per_pool: u32 = 256;
  58 
  59 const descriptor_types = [_]driver_mod.VkDescriptorType{
  60     driver_mod.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
  61     driver_mod.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
  62     driver_mod.VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
  63 };
  64 
  65 const pipeline_stage_2_all_commands: u64 = 0x10000;
  66 
  67 const all_access = driver_mod.VK_ACCESS_MEMORY_READ_BIT | driver_mod.VK_ACCESS_MEMORY_WRITE_BIT;
  68 
  69 pub const SamplerKey = struct {
  70     filter: driver_mod.VkFilter,
  71     address: driver_mod.VkSamplerAddressMode,
  72 };
  73 
  74 const max_samplers = 8;
  75 
  76 const DescriptorPool = struct {
  77     pool: VkDescriptorPool,
  78     live: u32,
  79 };
  80 
  81 /// The graphics state one backend keeps beside a runtime: a command pool for reusable command
  82 /// buffers, descriptor pools for binding sets, samplers shared by every set, and the device limits
  83 /// that shape images and pipelines.
  84 pub const Raster = struct {
  85     runtime: *Runtime,
  86     command_pool: VkCommandPool,
  87     descriptor_pools: std.ArrayListUnmanaged(DescriptorPool) = .empty,
  88     sampler_keys: [max_samplers]SamplerKey = undefined,
  89     samplers: [max_samplers]VkSampler = undefined,
  90     sampler_count: usize = 0,
  91     buffer_image_granularity: u64,
  92     max_image_extent: u32,
  93     max_vertex_buffers: u32,
  94     max_vertex_attributes: u32,
  95     /// The device's push-constant bytes, at most `max_push_constant_bytes`.
  96     max_push_constant_bytes: u32,
  97     /// Set when work that may read raster objects was submitted after the device last went idle.
  98     pending: bool = false,
  99 
 100     pub fn init(runtime: *Runtime) Error!Raster {
 101         if (!runtime.device.graphics()) return Error.FeatureNotPresent;
 102         const drv = &runtime._driver;
 103         var props: driver_mod.VkPhysicalDeviceProperties = undefined;
 104         drv.vkGetPhysicalDeviceProperties(runtime._physical_device, &props);
 105         const pool_ci = driver_mod.VkCommandPoolCreateInfo{
 106             .sType = driver_mod.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
 107             .pNext = null,
 108             .flags = driver_mod.VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
 109             .queueFamilyIndex = runtime.device.queueFamilyIndex(),
 110         };
 111         var pool: VkCommandPool = null;
 112         try drv.fromResult(drv.vkCreateCommandPool(runtime._logical_device, &pool_ci, null, &pool));
 113         return .{
 114             .runtime = runtime,
 115             .command_pool = pool,
 116             .buffer_image_granularity = @max(props.limits.bufferImageGranularity, 1),
 117             .max_image_extent = props.limits.maxImageDimension2D,
 118             .max_vertex_buffers = @min(props.limits.maxVertexInputBindings, max_vertex_buffers),
 119             .max_vertex_attributes = @min(props.limits.maxVertexInputAttributes, max_vertex_attributes),
 120             .max_push_constant_bytes = @min(props.limits.maxPushConstantsSize, max_push_constant_bytes),
 121         };
 122     }
 123 
 124     /// Waits for the device, then releases the pools and samplers. Every image, pipeline, set and
 125     /// bundle made from this raster must already be destroyed.
 126     pub fn deinit(self: *Raster) void {
 127         self.idle();
 128         const rt = self.runtime;
 129         const drv = &rt._driver;
 130         for (self.descriptor_pools.items) |entry| {
 131             std.debug.assert(entry.live == 0);
 132             drv.vkDestroyDescriptorPool(rt._logical_device, entry.pool, null);
 133         }
 134         self.descriptor_pools.deinit(rt.allocator);
 135         for (self.samplers[0..self.sampler_count]) |handle| {
 136             drv.vkDestroySampler(rt._logical_device, handle, null);
 137         }
 138         drv.vkDestroyCommandPool(rt._logical_device, self.command_pool, null);
 139         self.* = undefined;
 140     }
 141 
 142     /// Waits until the device has finished every submission that may still read a raster object.
 143     /// Destruction calls it, so a caller may destroy an object right after submitting work that
 144     /// names it.
 145     pub fn idle(self: *Raster) void {
 146         if (!self.pending) return;
 147         self.runtime.synchronize() catch |err| {
 148             std.log.scoped(.vulkan_raster).warn("device wait before destroy failed: {s}", .{@errorName(err)});
 149             return;
 150         };
 151         self.pending = false;
 152     }
 153 
 154     pub fn formatFeatures(self: *const Raster, format: VkFormat) driver_mod.VkFormatFeatureFlags {
 155         var props: driver_mod.VkFormatProperties = undefined;
 156         self.runtime._driver.vkGetPhysicalDeviceFormatProperties(self.runtime._physical_device, format, &props);
 157         return props.optimalTilingFeatures;
 158     }
 159 
 160     /// The sampler for `key`, made on first use and shared by every set that names it.
 161     pub fn sampler(self: *Raster, key: SamplerKey) Error!VkSampler {
 162         for (self.sampler_keys[0..self.sampler_count], self.samplers[0..self.sampler_count]) |existing, handle| {
 163             if (std.meta.eql(existing, key)) return handle;
 164         }
 165         if (self.sampler_count == max_samplers) return Error.TooManyObjects;
 166         const ci = driver_mod.VkSamplerCreateInfo{
 167             .sType = driver_mod.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
 168             .pNext = null,
 169             .flags = 0,
 170             .magFilter = key.filter,
 171             .minFilter = key.filter,
 172             .mipmapMode = driver_mod.VK_SAMPLER_MIPMAP_MODE_NEAREST,
 173             .addressModeU = key.address,
 174             .addressModeV = key.address,
 175             .addressModeW = key.address,
 176             .mipLodBias = 0,
 177             .anisotropyEnable = driver_mod.VK_FALSE,
 178             .maxAnisotropy = 1,
 179             .compareEnable = driver_mod.VK_FALSE,
 180             .compareOp = driver_mod.VK_COMPARE_OP_ALWAYS,
 181             .minLod = 0,
 182             .maxLod = 0,
 183             .borderColor = driver_mod.VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK,
 184             .unnormalizedCoordinates = driver_mod.VK_FALSE,
 185         };
 186         var handle: VkSampler = 0;
 187         const rt = self.runtime;
 188         try rt._driver.fromResult(rt._driver.vkCreateSampler(rt._logical_device, &ci, null, &handle));
 189         self.sampler_keys[self.sampler_count] = key;
 190         self.samplers[self.sampler_count] = handle;
 191         self.sampler_count += 1;
 192         return handle;
 193     }
 194 
 195     /// A primary command buffer from the raster's own pool, for a pass recorded once and
 196     /// submitted many times. Stream pools recycle their buffers, so a bundle cannot live there.
 197     pub fn allocateBundle(self: *Raster) Error!VkCommandBuffer {
 198         const rt = self.runtime;
 199         const info = driver_mod.VkCommandBufferAllocateInfo{
 200             .sType = driver_mod.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
 201             .pNext = null,
 202             .commandPool = self.command_pool,
 203             .level = driver_mod.VK_COMMAND_BUFFER_LEVEL_PRIMARY,
 204             .commandBufferCount = 1,
 205         };
 206         var cb: VkCommandBuffer = null;
 207         try rt._driver.fromResult(rt._driver.vkAllocateCommandBuffers(rt._logical_device, &info, @ptrCast(&cb)));
 208         return cb;
 209     }
 210 
 211     pub fn freeBundle(self: *Raster, cb: VkCommandBuffer) void {
 212         self.idle();
 213         self.discardBundle(cb);
 214     }
 215 
 216     /// Frees a bundle's command buffer that was never submitted, without waiting for the device.
 217     pub fn discardBundle(self: *Raster, cb: VkCommandBuffer) void {
 218         const rt = self.runtime;
 219         var handle = cb;
 220         rt._driver.vkFreeCommandBuffers(rt._logical_device, self.command_pool, 1, @ptrCast(&handle));
 221     }
 222 
 223     fn allocateSet(self: *Raster, layout: VkDescriptorSetLayout) Error!BindingSet {
 224         const rt = self.runtime;
 225         const drv = &rt._driver;
 226         var index: usize = 0;
 227         while (index <= self.descriptor_pools.items.len) : (index += 1) {
 228             if (index == self.descriptor_pools.items.len) try self.addDescriptorPool();
 229             const entry = &self.descriptor_pools.items[index];
 230             if (entry.live == sets_per_pool) continue;
 231             var set_layout = layout;
 232             const info = driver_mod.VkDescriptorSetAllocateInfo{
 233                 .sType = driver_mod.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
 234                 .pNext = null,
 235                 .descriptorPool = entry.pool,
 236                 .descriptorSetCount = 1,
 237                 .pSetLayouts = @ptrCast(&set_layout),
 238             };
 239             var set: VkDescriptorSet = null;
 240             drv.fromResult(drv.vkAllocateDescriptorSets(rt._logical_device, &info, @ptrCast(&set))) catch |err| switch (err) {
 241                 error.FragmentedPool, error.OutOfPoolMemory => continue,
 242                 else => return err,
 243             };
 244             entry.live += 1;
 245             return .{ .set = set, .pool_index = @intCast(index) };
 246         }
 247         unreachable;
 248     }
 249 
 250     fn addDescriptorPool(self: *Raster) Error!void {
 251         const rt = self.runtime;
 252         var sizes: [descriptor_types.len]driver_mod.VkDescriptorPoolSize = undefined;
 253         for (&sizes, descriptor_types) |*size, kind| {
 254             size.* = .{ .type = kind, .descriptorCount = sets_per_pool * max_descriptors };
 255         }
 256         const ci = driver_mod.VkDescriptorPoolCreateInfo{
 257             .sType = driver_mod.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
 258             .pNext = null,
 259             .flags = driver_mod.VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
 260             .maxSets = sets_per_pool,
 261             .poolSizeCount = sizes.len,
 262             .pPoolSizes = &sizes,
 263         };
 264         var pool: VkDescriptorPool = null;
 265         try rt._driver.fromResult(rt._driver.vkCreateDescriptorPool(rt._logical_device, &ci, null, &pool));
 266         errdefer rt._driver.vkDestroyDescriptorPool(rt._logical_device, pool, null);
 267         self.descriptor_pools.append(rt.allocator, .{ .pool = pool, .live = 0 }) catch return Error.OutOfHostMemory;
 268     }
 269 };
 270 
 271 /// The layout an image rests in between passes and transfers. A sampled image rests where a
 272 /// shader can read it, an attachment where a pass writes it, and anything else in the general
 273 /// layout.
 274 pub fn homeLayout(usage: VkImageUsageFlags) VkImageLayout {
 275     if (usage & driver_mod.VK_IMAGE_USAGE_SAMPLED_BIT != 0) return driver_mod.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
 276     if (usage & driver_mod.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT != 0) return driver_mod.VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
 277     if (usage & driver_mod.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT != 0) return driver_mod.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 278     return driver_mod.VK_IMAGE_LAYOUT_GENERAL;
 279 }
 280 
 281 pub const ImageDesc = struct {
 282     width: u32,
 283     height: u32,
 284     format: VkFormat,
 285     usage: VkImageUsageFlags,
 286     aspect: VkImageAspectFlags,
 287     /// Bytes one texel occupies in a tightly packed transfer.
 288     texel_bytes: u32,
 289 };
 290 
 291 /// The memory behind an image: a range of a pooled block, or an allocation of its own that another
 292 /// process may share, which the image frees with itself.
 293 pub const Backing = union(enum) {
 294     pooled: memory_mod.Allocation,
 295     dedicated: driver_mod.VkDeviceMemory,
 296 };
 297 
 298 /// A 2D image with one mip level and one layer, its view, and the memory behind it. It rests in
 299 /// `home` whenever no command buffer is using it.
 300 pub const Image = struct {
 301     image: VkImage,
 302     view: VkImageView,
 303     backing: Backing,
 304     width: u32,
 305     height: u32,
 306     format: VkFormat,
 307     aspect: VkImageAspectFlags,
 308     texel_bytes: u32,
 309     home: VkImageLayout,
 310 
 311     /// Makes the image and moves it into its home layout before returning, so every later
 312     /// command buffer can assume it.
 313     pub fn create(raster: *Raster, desc: ImageDesc) Error!Image {
 314         std.debug.assert(desc.texel_bytes != 0);
 315         if (desc.width == 0 or desc.height == 0) return Error.InitializationFailed;
 316         if (desc.width > raster.max_image_extent or desc.height > raster.max_image_extent) return Error.InitializationFailed;
 317         const rt = raster.runtime;
 318         const drv = &rt._driver;
 319         const ci = driver_mod.VkImageCreateInfo{
 320             .sType = driver_mod.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
 321             .pNext = null,
 322             .flags = 0,
 323             .imageType = driver_mod.VK_IMAGE_TYPE_2D,
 324             .format = desc.format,
 325             .extent = .{ .width = desc.width, .height = desc.height, .depth = 1 },
 326             .mipLevels = 1,
 327             .arrayLayers = 1,
 328             .samples = driver_mod.VK_SAMPLE_COUNT_1_BIT,
 329             .tiling = driver_mod.VK_IMAGE_TILING_OPTIMAL,
 330             .usage = desc.usage,
 331             .sharingMode = driver_mod.VK_SHARING_MODE_EXCLUSIVE,
 332             .queueFamilyIndexCount = 0,
 333             .pQueueFamilyIndices = null,
 334             .initialLayout = driver_mod.VK_IMAGE_LAYOUT_UNDEFINED,
 335         };
 336         var image: VkImage = 0;
 337         try drv.fromResult(drv.vkCreateImage(rt._logical_device, &ci, null, &image));
 338         errdefer drv.vkDestroyImage(rt._logical_device, image, null);
 339 
 340         var requirements: driver_mod.VkMemoryRequirements = undefined;
 341         drv.vkGetImageMemoryRequirements(rt._logical_device, image, &requirements);
 342         const allocation = try rt._memory.allocate(drv, rt._logical_device, granular(requirements, raster.buffer_image_granularity), .device);
 343         errdefer rt._memory.release(allocation);
 344         try drv.fromResult(drv.vkBindImageMemory(
 345             rt._logical_device,
 346             image,
 347             rt._memory.deviceMemory(allocation),
 348             allocation.offset,
 349         ));
 350 
 351         const view_ci = driver_mod.VkImageViewCreateInfo{
 352             .sType = driver_mod.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
 353             .pNext = null,
 354             .flags = 0,
 355             .image = image,
 356             .viewType = driver_mod.VK_IMAGE_VIEW_TYPE_2D,
 357             .format = desc.format,
 358             .components = .{},
 359             .subresourceRange = subresourceRange(desc.aspect),
 360         };
 361         var view: VkImageView = 0;
 362         try drv.fromResult(drv.vkCreateImageView(rt._logical_device, &view_ci, null, &view));
 363         errdefer drv.vkDestroyImageView(rt._logical_device, view, null);
 364 
 365         const created = Image{
 366             .image = image,
 367             .view = view,
 368             .backing = .{ .pooled = allocation },
 369             .width = desc.width,
 370             .height = desc.height,
 371             .format = desc.format,
 372             .aspect = desc.aspect,
 373             .texel_bytes = desc.texel_bytes,
 374             .home = homeLayout(desc.usage),
 375         };
 376         const stream = try rt.defaultStream();
 377         const cb = try beginOneShot(stream);
 378         recordBarrier(drv, cb, &.{created.transition(driver_mod.VK_IMAGE_LAYOUT_UNDEFINED, created.home)});
 379         try finishOneShot(stream, cb);
 380         return created;
 381     }
 382 
 383     pub fn destroy(self: *Image, raster: *Raster) void {
 384         raster.idle();
 385         const rt = raster.runtime;
 386         rt._driver.vkDestroyImageView(rt._logical_device, self.view, null);
 387         rt._driver.vkDestroyImage(rt._logical_device, self.image, null);
 388         switch (self.backing) {
 389             .pooled => |allocation| rt._memory.release(allocation),
 390             .dedicated => |memory| rt._driver.vkFreeMemory(rt._logical_device, memory, null),
 391         }
 392         self.* = undefined;
 393     }
 394 
 395     pub fn byteSize(self: *const Image) usize {
 396         return @as(usize, self.width) * @as(usize, self.height) * @as(usize, self.texel_bytes);
 397     }
 398 
 399     /// Replaces every texel with `bytes`, tightly packed rows from the top.
 400     pub fn write(self: *const Image, raster: *Raster, bytes: []const u8) Error!void {
 401         if (bytes.len != self.byteSize()) return Error.InitializationFailed;
 402         const rt = raster.runtime;
 403         var staging = try runtime_mod.TransferBuffer.init(rt, bytes.len);
 404         defer staging.deinit();
 405         const mapped = rt._memory.mapped(staging.allocation) orelse return Error.MemoryMapFailed;
 406         @memcpy(mapped[0..bytes.len], bytes);
 407         try rt._memory.flush(&rt._driver, rt._logical_device, staging.allocation);
 408         try self.copy(raster, staging.buffer, .upload);
 409     }
 410 
 411     /// Copies every texel into `bytes`, tightly packed rows from the top.
 412     pub fn read(self: *const Image, raster: *Raster, bytes: []u8) Error!void {
 413         if (bytes.len != self.byteSize()) return Error.InitializationFailed;
 414         const rt = raster.runtime;
 415         var staging = try runtime_mod.TransferBuffer.init(rt, bytes.len);
 416         defer staging.deinit();
 417         try self.copy(raster, staging.buffer, .download);
 418         try rt._memory.invalidate(&rt._driver, rt._logical_device, staging.allocation);
 419         const mapped = rt._memory.mapped(staging.allocation) orelse return Error.MemoryMapFailed;
 420         @memcpy(bytes, mapped[0..bytes.len]);
 421     }
 422 
 423     /// Records and waits for one copy between the image and `buffer` on the default stream.
 424     fn copy(self: *const Image, raster: *Raster, buffer: VkBuffer, direction: enum { upload, download }) Error!void {
 425         const rt = raster.runtime;
 426         const drv = &rt._driver;
 427         const stream = try rt.defaultStream();
 428         const cb = try beginOneShot(stream);
 429         const transfer_layout = switch (direction) {
 430             .upload => driver_mod.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
 431             .download => driver_mod.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
 432         };
 433         recordBarrier(drv, cb, &.{self.transition(self.home, transfer_layout)});
 434         const region = driver_mod.VkBufferImageCopy{
 435             .bufferOffset = 0,
 436             .bufferRowLength = 0,
 437             .bufferImageHeight = 0,
 438             .imageSubresource = .{ .aspectMask = self.aspect, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1 },
 439             .imageOffset = .{ .x = 0, .y = 0, .z = 0 },
 440             .imageExtent = .{ .width = self.width, .height = self.height, .depth = 1 },
 441         };
 442         switch (direction) {
 443             .upload => drv.vkCmdCopyBufferToImage(cb, buffer, self.image, transfer_layout, 1, @ptrCast(&region)),
 444             .download => drv.vkCmdCopyImageToBuffer(cb, self.image, transfer_layout, buffer, 1, @ptrCast(&region)),
 445         }
 446         recordBarrier(drv, cb, &.{self.transition(transfer_layout, self.home)});
 447         try finishOneShot(stream, cb);
 448     }
 449 
 450     /// A barrier that moves the whole image from `old` to `new` after all earlier work on the queue.
 451     pub fn transition(self: *const Image, old: VkImageLayout, new: VkImageLayout) driver_mod.VkImageMemoryBarrier {
 452         return .{
 453             .sType = driver_mod.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
 454             .pNext = null,
 455             .srcAccessMask = driver_mod.VK_ACCESS_MEMORY_WRITE_BIT,
 456             .dstAccessMask = all_access,
 457             .oldLayout = old,
 458             .newLayout = new,
 459             .srcQueueFamilyIndex = driver_mod.VK_QUEUE_FAMILY_IGNORED,
 460             .dstQueueFamilyIndex = driver_mod.VK_QUEUE_FAMILY_IGNORED,
 461             .image = self.image,
 462             .subresourceRange = subresourceRange(self.aspect),
 463         };
 464     }
 465 };
 466 
 467 pub fn subresourceRange(aspect: VkImageAspectFlags) driver_mod.VkImageSubresourceRange {
 468     return .{ .aspectMask = aspect, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1 };
 469 }
 470 
 471 /// Pads an image's requirements to whole granularity pages, so an optimal-tiling image never
 472 /// shares a page with a linear buffer in the same memory block.
 473 fn granular(requirements: driver_mod.VkMemoryRequirements, granularity: u64) driver_mod.VkMemoryRequirements {
 474     std.debug.assert(granularity != 0);
 475     return .{
 476         .size = std.mem.alignForward(u64, requirements.size, granularity),
 477         .alignment = @max(requirements.alignment, granularity),
 478         .memoryTypeBits = requirements.memoryTypeBits,
 479     };
 480 }
 481 
 482 /// A full barrier over all earlier work on the queue: every write before it becomes visible to
 483 /// every read and write after it, and each image in `images` changes layout in between.
 484 pub fn recordBarrier(drv: *const Driver, cb: VkCommandBuffer, images: []const driver_mod.VkImageMemoryBarrier) void {
 485     const memory = driver_mod.VkMemoryBarrier{
 486         .srcAccessMask = driver_mod.VK_ACCESS_MEMORY_WRITE_BIT,
 487         .dstAccessMask = all_access,
 488     };
 489     drv.vkCmdPipelineBarrier(
 490         cb,
 491         driver_mod.VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
 492         driver_mod.VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
 493         0,
 494         1,
 495         @ptrCast(&memory),
 496         0,
 497         null,
 498         @intCast(images.len),
 499         if (images.len == 0) null else images.ptr,
 500     );
 501 }
 502 
 503 /// Makes host reads of transfer writes safe once the host has waited for the submission.
 504 fn recordHostBarrier(drv: *const Driver, cb: VkCommandBuffer) void {
 505     const memory = driver_mod.VkMemoryBarrier{
 506         .srcAccessMask = driver_mod.VK_ACCESS_MEMORY_WRITE_BIT,
 507         .dstAccessMask = driver_mod.VK_ACCESS_HOST_READ_BIT,
 508     };
 509     drv.vkCmdPipelineBarrier(
 510         cb,
 511         driver_mod.VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
 512         driver_mod.VK_PIPELINE_STAGE_HOST_BIT,
 513         0,
 514         1,
 515         @ptrCast(&memory),
 516         0,
 517         null,
 518         0,
 519         null,
 520     );
 521 }
 522 
 523 fn beginOneShot(stream: *Stream) Error!VkCommandBuffer {
 524     if (stream.poisoned) return Error.StreamPoisoned;
 525     const cb = try runtime_mod.acquireStreamCommandBuffer(stream);
 526     try beginCommandBuffer(&stream._runtime._driver, cb, driver_mod.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
 527     return cb;
 528 }
 529 
 530 /// Ends, submits and waits for a command buffer from `beginOneShot`. A failure poisons the stream,
 531 /// since the image layouts it promised may not hold.
 532 fn finishOneShot(stream: *Stream, cb: VkCommandBuffer) Error!void {
 533     errdefer stream.poisoned = true;
 534     const drv = &stream._runtime._driver;
 535     recordHostBarrier(drv, cb);
 536     try drv.fromResult(drv.vkEndCommandBuffer(cb));
 537     try runtime_mod.submitStreamCommandBuffer(stream, cb);
 538     try stream.synchronize();
 539 }
 540 
 541 pub fn beginCommandBuffer(drv: *const Driver, cb: VkCommandBuffer, flags: driver_mod.VkCommandBufferUsageFlags) Error!void {
 542     const info = driver_mod.VkCommandBufferBeginInfo{
 543         .sType = driver_mod.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
 544         .pNext = null,
 545         .flags = flags,
 546         .pInheritanceInfo = null,
 547     };
 548     try drv.fromResult(drv.vkBeginCommandBuffer(cb, &info));
 549 }
 550 
 551 /// How a fragment's color combines with the target's. The equations are the contract's
 552 /// `RenderBlendMode` equations, spelled in Vulkan blend factors by `blendState`.
 553 pub const Blend = enum {
 554     replace,
 555     alpha_premultiplied,
 556     alpha_straight,
 557     additive,
 558 };
 559 
 560 pub fn blendState(blend: Blend) driver_mod.VkPipelineColorBlendAttachmentState {
 561     const one = driver_mod.VK_BLEND_FACTOR_ONE;
 562     const inverse_alpha = driver_mod.VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
 563     const color_factors: [2]driver_mod.VkBlendFactor, const alpha_factors: [2]driver_mod.VkBlendFactor = switch (blend) {
 564         .replace => .{ .{ one, driver_mod.VK_BLEND_FACTOR_ZERO }, .{ one, driver_mod.VK_BLEND_FACTOR_ZERO } },
 565         .alpha_premultiplied => .{ .{ one, inverse_alpha }, .{ one, inverse_alpha } },
 566         .alpha_straight => .{ .{ driver_mod.VK_BLEND_FACTOR_SRC_ALPHA, inverse_alpha }, .{ one, inverse_alpha } },
 567         .additive => .{ .{ one, one }, .{ one, one } },
 568     };
 569     return .{
 570         .blendEnable = if (blend == .replace) driver_mod.VK_FALSE else driver_mod.VK_TRUE,
 571         .srcColorBlendFactor = color_factors[0],
 572         .dstColorBlendFactor = color_factors[1],
 573         .colorBlendOp = driver_mod.VK_BLEND_OP_ADD,
 574         .srcAlphaBlendFactor = alpha_factors[0],
 575         .dstAlphaBlendFactor = alpha_factors[1],
 576         .alphaBlendOp = driver_mod.VK_BLEND_OP_ADD,
 577         .colorWriteMask = driver_mod.VK_COLOR_COMPONENT_R_BIT | driver_mod.VK_COLOR_COMPONENT_G_BIT |
 578             driver_mod.VK_COLOR_COMPONENT_B_BIT | driver_mod.VK_COLOR_COMPONENT_A_BIT,
 579     };
 580 }
 581 
 582 pub const DepthTest = struct {
 583     format: VkFormat,
 584     compare: driver_mod.VkCompareOp,
 585     write: bool,
 586     bias_constant: f32 = 0,
 587     bias_slope: f32 = 0,
 588     bias_clamp: f32 = 0,
 589 
 590     fn biased(self: DepthTest) bool {
 591         return self.bias_constant != 0 or self.bias_slope != 0;
 592     }
 593 };
 594 
 595 /// The stages a push-constant range reaches: both, since either stage may read the block.
 596 pub const push_constant_stages = driver_mod.VK_SHADER_STAGE_VERTEX_BIT | driver_mod.VK_SHADER_STAGE_FRAGMENT_BIT;
 597 
 598 /// A graphics pipeline in Vulkan terms. Both entries come from one SPIR-V module, every descriptor
 599 /// lives in set 0 and is visible to both stages, and viewport and scissor are set per pass. A
 600 /// nonzero `push_constant_bytes` declares one range at offset 0 that both stages see.
 601 pub const PipelineDesc = struct {
 602     words: []const u32,
 603     vertex_entry: [*:0]const u8,
 604     fragment_entry: [*:0]const u8,
 605     color_format: VkFormat,
 606     depth: ?DepthTest,
 607     blend: Blend,
 608     topology: driver_mod.VkPrimitiveTopology,
 609     vertex_bindings: []const driver_mod.VkVertexInputBindingDescription,
 610     vertex_attributes: []const driver_mod.VkVertexInputAttributeDescription,
 611     descriptors: []const VkDescriptorSetLayoutBinding,
 612     push_constant_bytes: u32 = 0,
 613 };
 614 
 615 pub const Pipeline = struct {
 616     pipeline: VkPipeline,
 617     layout: VkPipelineLayout,
 618     set_layout: VkDescriptorSetLayout,
 619 
 620     /// Front faces wind counter-clockwise in framebuffer space, where y grows down, and no face
 621     /// is culled.
 622     pub fn create(raster: *Raster, desc: PipelineDesc) Error!Pipeline {
 623         if (desc.descriptors.len > max_descriptors) return Error.InitializationFailed;
 624         if (desc.vertex_bindings.len > raster.max_vertex_buffers) return Error.InitializationFailed;
 625         if (desc.vertex_attributes.len > raster.max_vertex_attributes) return Error.InitializationFailed;
 626         const rt = raster.runtime;
 627         const drv = &rt._driver;
 628         const module = try rt._shader_cache.populateCache(rt, desc.words);
 629 
 630         const set_ci = driver_mod.VkDescriptorSetLayoutCreateInfo{
 631             .sType = driver_mod.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
 632             .pNext = null,
 633             .flags = 0,
 634             .bindingCount = @intCast(desc.descriptors.len),
 635             .pBindings = if (desc.descriptors.len == 0) null else desc.descriptors.ptr,
 636         };
 637         var set_layout: VkDescriptorSetLayout = null;
 638         try drv.fromResult(drv.vkCreateDescriptorSetLayout(rt._logical_device, &set_ci, null, &set_layout));
 639         errdefer drv.vkDestroyDescriptorSetLayout(rt._logical_device, set_layout, null);
 640 
 641         std.debug.assert(desc.push_constant_bytes % 4 == 0);
 642         const push_range = driver_mod.VkPushConstantRange{
 643             .stageFlags = push_constant_stages,
 644             .offset = 0,
 645             .size = desc.push_constant_bytes,
 646         };
 647         const pushes = desc.push_constant_bytes != 0;
 648         const layout_ci = driver_mod.VkPipelineLayoutCreateInfo{
 649             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
 650             .pNext = null,
 651             .flags = 0,
 652             .setLayoutCount = 1,
 653             .pSetLayouts = @ptrCast(&set_layout),
 654             .pushConstantRangeCount = if (pushes) 1 else 0,
 655             .pPushConstantRanges = if (pushes) @ptrCast(&push_range) else null,
 656         };
 657         var layout: VkPipelineLayout = null;
 658         try drv.fromResult(drv.vkCreatePipelineLayout(rt._logical_device, &layout_ci, null, &layout));
 659         errdefer drv.vkDestroyPipelineLayout(rt._logical_device, layout, null);
 660 
 661         const stages = [2]driver_mod.VkPipelineShaderStageCreateInfo{
 662             .{
 663                 .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
 664                 .pNext = null,
 665                 .flags = 0,
 666                 .stage = driver_mod.VK_SHADER_STAGE_VERTEX_BIT,
 667                 .module = module,
 668                 .pName = desc.vertex_entry,
 669                 .pSpecializationInfo = null,
 670             },
 671             .{
 672                 .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
 673                 .pNext = null,
 674                 .flags = 0,
 675                 .stage = driver_mod.VK_SHADER_STAGE_FRAGMENT_BIT,
 676                 .module = module,
 677                 .pName = desc.fragment_entry,
 678                 .pSpecializationInfo = null,
 679             },
 680         };
 681         const vertex_input = driver_mod.VkPipelineVertexInputStateCreateInfo{
 682             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
 683             .pNext = null,
 684             .flags = 0,
 685             .vertexBindingDescriptionCount = @intCast(desc.vertex_bindings.len),
 686             .pVertexBindingDescriptions = if (desc.vertex_bindings.len == 0) null else desc.vertex_bindings.ptr,
 687             .vertexAttributeDescriptionCount = @intCast(desc.vertex_attributes.len),
 688             .pVertexAttributeDescriptions = if (desc.vertex_attributes.len == 0) null else desc.vertex_attributes.ptr,
 689         };
 690         const input_assembly = driver_mod.VkPipelineInputAssemblyStateCreateInfo{
 691             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
 692             .pNext = null,
 693             .flags = 0,
 694             .topology = desc.topology,
 695             .primitiveRestartEnable = driver_mod.VK_FALSE,
 696         };
 697         const viewport = driver_mod.VkPipelineViewportStateCreateInfo{
 698             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
 699             .pNext = null,
 700             .flags = 0,
 701             .viewportCount = 1,
 702             .pViewports = null,
 703             .scissorCount = 1,
 704             .pScissors = null,
 705         };
 706         const depth_test: DepthTest = desc.depth orelse .{
 707             .format = driver_mod.VK_FORMAT_UNDEFINED,
 708             .compare = driver_mod.VK_COMPARE_OP_ALWAYS,
 709             .write = false,
 710         };
 711         const rasterization = driver_mod.VkPipelineRasterizationStateCreateInfo{
 712             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
 713             .pNext = null,
 714             .flags = 0,
 715             .depthClampEnable = driver_mod.VK_FALSE,
 716             .rasterizerDiscardEnable = driver_mod.VK_FALSE,
 717             .polygonMode = driver_mod.VK_POLYGON_MODE_FILL,
 718             .cullMode = driver_mod.VK_CULL_MODE_NONE,
 719             .frontFace = driver_mod.VK_FRONT_FACE_COUNTER_CLOCKWISE,
 720             .depthBiasEnable = if (depth_test.biased()) driver_mod.VK_TRUE else driver_mod.VK_FALSE,
 721             .depthBiasConstantFactor = depth_test.bias_constant,
 722             .depthBiasClamp = depth_test.bias_clamp,
 723             .depthBiasSlopeFactor = depth_test.bias_slope,
 724             .lineWidth = 1,
 725         };
 726         const multisample = driver_mod.VkPipelineMultisampleStateCreateInfo{
 727             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
 728             .pNext = null,
 729             .flags = 0,
 730             .rasterizationSamples = driver_mod.VK_SAMPLE_COUNT_1_BIT,
 731             .sampleShadingEnable = driver_mod.VK_FALSE,
 732             .minSampleShading = 0,
 733             .pSampleMask = null,
 734             .alphaToCoverageEnable = driver_mod.VK_FALSE,
 735             .alphaToOneEnable = driver_mod.VK_FALSE,
 736         };
 737         const depth_stencil = if (desc.depth) |depth| driver_mod.VkPipelineDepthStencilStateCreateInfo{
 738             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
 739             .pNext = null,
 740             .flags = 0,
 741             .depthTestEnable = driver_mod.VK_TRUE,
 742             .depthWriteEnable = if (depth.write) driver_mod.VK_TRUE else driver_mod.VK_FALSE,
 743             .depthCompareOp = depth.compare,
 744             .depthBoundsTestEnable = driver_mod.VK_FALSE,
 745             .stencilTestEnable = driver_mod.VK_FALSE,
 746             .front = .{},
 747             .back = .{},
 748             .minDepthBounds = 0,
 749             .maxDepthBounds = 1,
 750         } else null;
 751         const attachment = blendState(desc.blend);
 752         const color_blend = driver_mod.VkPipelineColorBlendStateCreateInfo{
 753             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
 754             .pNext = null,
 755             .flags = 0,
 756             .logicOpEnable = driver_mod.VK_FALSE,
 757             .logicOp = 0,
 758             .attachmentCount = 1,
 759             .pAttachments = @ptrCast(&attachment),
 760             .blendConstants = .{ 0, 0, 0, 0 },
 761         };
 762         const dynamic_states = [_]driver_mod.VkDynamicState{
 763             driver_mod.VK_DYNAMIC_STATE_VIEWPORT,
 764             driver_mod.VK_DYNAMIC_STATE_SCISSOR,
 765         };
 766         const dynamic = driver_mod.VkPipelineDynamicStateCreateInfo{
 767             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
 768             .pNext = null,
 769             .flags = 0,
 770             .dynamicStateCount = dynamic_states.len,
 771             .pDynamicStates = &dynamic_states,
 772         };
 773         var color_format = desc.color_format;
 774         const rendering = driver_mod.VkPipelineRenderingCreateInfo{
 775             .sType = driver_mod.VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
 776             .pNext = null,
 777             .viewMask = 0,
 778             .colorAttachmentCount = 1,
 779             .pColorAttachmentFormats = @ptrCast(&color_format),
 780             .depthAttachmentFormat = if (desc.depth) |depth| depth.format else driver_mod.VK_FORMAT_UNDEFINED,
 781             .stencilAttachmentFormat = driver_mod.VK_FORMAT_UNDEFINED,
 782         };
 783         const ci = driver_mod.VkGraphicsPipelineCreateInfo{
 784             .sType = driver_mod.VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
 785             .pNext = @ptrCast(&rendering),
 786             .flags = 0,
 787             .stageCount = stages.len,
 788             .pStages = &stages,
 789             .pVertexInputState = &vertex_input,
 790             .pInputAssemblyState = &input_assembly,
 791             .pTessellationState = null,
 792             .pViewportState = &viewport,
 793             .pRasterizationState = &rasterization,
 794             .pMultisampleState = &multisample,
 795             .pDepthStencilState = if (depth_stencil) |*state| state else null,
 796             .pColorBlendState = &color_blend,
 797             .pDynamicState = &dynamic,
 798             .layout = layout,
 799             .renderPass = 0,
 800             .subpass = 0,
 801             .basePipelineHandle = null,
 802             .basePipelineIndex = -1,
 803         };
 804         var pipeline: VkPipeline = null;
 805         try drv.fromResult(drv.vkCreateGraphicsPipelines(rt._logical_device, null, 1, @ptrCast(&ci), null, @ptrCast(&pipeline)));
 806         return .{ .pipeline = pipeline, .layout = layout, .set_layout = set_layout };
 807     }
 808 
 809     pub fn destroy(self: *Pipeline, raster: *Raster) void {
 810         raster.idle();
 811         const rt = raster.runtime;
 812         rt._driver.vkDestroyPipeline(rt._logical_device, self.pipeline, null);
 813         rt._driver.vkDestroyPipelineLayout(rt._logical_device, self.layout, null);
 814         rt._driver.vkDestroyDescriptorSetLayout(rt._logical_device, self.set_layout, null);
 815         self.* = undefined;
 816     }
 817 };
 818 
 819 /// One resource for one binding of set 0.
 820 pub const Descriptor = struct {
 821     binding: u32,
 822     resource: union(enum) {
 823         buffer: struct { buffer: VkBuffer, kind: driver_mod.VkDescriptorType },
 824         image: struct { view: VkImageView, layout: VkImageLayout, sampler: VkSampler },
 825     },
 826 };
 827 
 828 /// A descriptor set written once when it is made and bound by every draw that names it.
 829 pub const BindingSet = struct {
 830     set: VkDescriptorSet,
 831     pool_index: u32,
 832 
 833     pub fn create(raster: *Raster, pipeline: *const Pipeline, descriptors: []const Descriptor) Error!BindingSet {
 834         if (descriptors.len > max_descriptors) return Error.InitializationFailed;
 835         var created = try raster.allocateSet(pipeline.set_layout);
 836         errdefer created.destroy(raster);
 837         var buffers: [max_descriptors]driver_mod.VkDescriptorBufferInfo = undefined;
 838         var images: [max_descriptors]driver_mod.VkDescriptorImageInfo = undefined;
 839         var writes: [max_descriptors]driver_mod.VkWriteDescriptorSet = undefined;
 840         for (descriptors, 0..) |descriptor, index| {
 841             writes[index] = .{
 842                 .sType = driver_mod.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
 843                 .pNext = null,
 844                 .dstSet = created.set,
 845                 .dstBinding = descriptor.binding,
 846                 .dstArrayElement = 0,
 847                 .descriptorCount = 1,
 848                 .descriptorType = undefined,
 849                 .pImageInfo = null,
 850                 .pBufferInfo = null,
 851                 .pTexelBufferView = null,
 852             };
 853             switch (descriptor.resource) {
 854                 .buffer => |buffer| {
 855                     buffers[index] = .{ .buffer = buffer.buffer, .offset = 0, .range = driver_mod.VK_WHOLE_SIZE };
 856                     writes[index].descriptorType = buffer.kind;
 857                     writes[index].pBufferInfo = @ptrCast(&buffers[index]);
 858                 },
 859                 .image => |image| {
 860                     images[index] = .{ .sampler = image.sampler, .imageView = image.view, .imageLayout = image.layout };
 861                     writes[index].descriptorType = driver_mod.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
 862                     writes[index].pImageInfo = @ptrCast(&images[index]);
 863                 },
 864             }
 865         }
 866         const rt = raster.runtime;
 867         rt._driver.vkUpdateDescriptorSets(rt._logical_device, @intCast(descriptors.len), @ptrCast(&writes), 0, null);
 868         return created;
 869     }
 870 
 871     pub fn destroy(self: *BindingSet, raster: *Raster) void {
 872         raster.idle();
 873         const rt = raster.runtime;
 874         const entry = &raster.descriptor_pools.items[self.pool_index];
 875         std.debug.assert(entry.live != 0);
 876         var set = self.set;
 877         _ = rt._driver.vkFreeDescriptorSets(rt._logical_device, entry.pool, 1, @ptrCast(&set));
 878         entry.live -= 1;
 879         self.* = undefined;
 880     }
 881 };
 882 
 883 /// A pass attachment and whether the pass clears it to `clear` or keeps its contents.
 884 pub const Attachment = struct {
 885     image: *const Image,
 886     clear: ?driver_mod.VkClearValue,
 887 };
 888 
 889 pub const Targets = struct {
 890     color: Attachment,
 891     depth: ?Attachment,
 892     viewport: driver_mod.VkViewport,
 893     scissor: driver_mod.VkRect2D,
 894 };
 895 
 896 const color_layout = driver_mod.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 897 const depth_layout = driver_mod.VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
 898 
 899 /// Opens a pass over the whole color target: a full barrier, each attachment moved out of its home
 900 /// layout, then dynamic rendering with the pass's viewport and scissor.
 901 pub fn beginPass(drv: *const Driver, cb: VkCommandBuffer, targets: Targets) void {
 902     var barriers: [2]driver_mod.VkImageMemoryBarrier = undefined;
 903     var count: usize = 0;
 904     const color = targets.color.image;
 905     if (color.home != color_layout) {
 906         barriers[count] = color.transition(color.home, color_layout);
 907         count += 1;
 908     }
 909     if (targets.depth) |depth| if (depth.image.home != depth_layout) {
 910         barriers[count] = depth.image.transition(depth.image.home, depth_layout);
 911         count += 1;
 912     };
 913     recordBarrier(drv, cb, barriers[0..count]);
 914 
 915     const color_info = attachmentInfo(targets.color, color_layout);
 916     const depth_info = if (targets.depth) |depth| attachmentInfo(depth, depth_layout) else undefined;
 917     const info = driver_mod.VkRenderingInfo{
 918         .sType = driver_mod.VK_STRUCTURE_TYPE_RENDERING_INFO,
 919         .pNext = null,
 920         .flags = 0,
 921         .renderArea = .{ .offset = .{ .x = 0, .y = 0 }, .extent = .{ .width = color.width, .height = color.height } },
 922         .layerCount = 1,
 923         .viewMask = 0,
 924         .colorAttachmentCount = 1,
 925         .pColorAttachments = @ptrCast(&color_info),
 926         .pDepthAttachment = if (targets.depth != null) &depth_info else null,
 927         .pStencilAttachment = null,
 928     };
 929     drv.vkCmdBeginRendering(cb, &info);
 930     drv.vkCmdSetViewport(cb, 0, 1, @ptrCast(&targets.viewport));
 931     drv.vkCmdSetScissor(cb, 0, 1, @ptrCast(&targets.scissor));
 932 }
 933 
 934 /// Closes a pass and returns each attachment to its home layout behind a full barrier.
 935 pub fn endPass(drv: *const Driver, cb: VkCommandBuffer, targets: Targets) void {
 936     drv.vkCmdEndRendering(cb);
 937     var barriers: [2]driver_mod.VkImageMemoryBarrier = undefined;
 938     var count: usize = 0;
 939     const color = targets.color.image;
 940     if (color.home != color_layout) {
 941         barriers[count] = color.transition(color_layout, color.home);
 942         count += 1;
 943     }
 944     if (targets.depth) |depth| if (depth.image.home != depth_layout) {
 945         barriers[count] = depth.image.transition(depth_layout, depth.image.home);
 946         count += 1;
 947     };
 948     recordBarrier(drv, cb, barriers[0..count]);
 949 }
 950 
 951 fn attachmentInfo(attachment: Attachment, layout: VkImageLayout) driver_mod.VkRenderingAttachmentInfo {
 952     return .{
 953         .sType = driver_mod.VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
 954         .pNext = null,
 955         .imageView = attachment.image.view,
 956         .imageLayout = layout,
 957         .resolveMode = driver_mod.VK_RESOLVE_MODE_NONE,
 958         .resolveImageView = 0,
 959         .resolveImageLayout = driver_mod.VK_IMAGE_LAYOUT_UNDEFINED,
 960         .loadOp = if (attachment.clear != null) driver_mod.VK_ATTACHMENT_LOAD_OP_CLEAR else driver_mod.VK_ATTACHMENT_LOAD_OP_LOAD,
 961         .storeOp = driver_mod.VK_ATTACHMENT_STORE_OP_STORE,
 962         .clearValue = attachment.clear orelse std.mem.zeroes(driver_mod.VkClearValue),
 963     };
 964 }
 965 
 966 /// A command buffer from `stream`'s pool, begun for one submission.
 967 pub fn beginStreamPass(stream: *Stream) Error!VkCommandBuffer {
 968     return beginOneShot(stream);
 969 }
 970 
 971 /// A command buffer from the raster's pool, begun so it may be pending more than once.
 972 pub fn beginBundle(raster: *Raster) Error!VkCommandBuffer {
 973     const cb = try raster.allocateBundle();
 974     errdefer raster.discardBundle(cb);
 975     try beginCommandBuffer(&raster.runtime._driver, cb, driver_mod.VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT);
 976     return cb;
 977 }
 978 
 979 pub const Submission = struct {
 980     stream: *Stream,
 981     cb: VkCommandBuffer,
 982     wait_events: []const *Event,
 983     signal_event: ?*Event,
 984     /// Whether the stream takes the command buffer back for reuse once the submission finishes.
 985     /// A bundle's command buffer stays with its bundle.
 986     retire: bool,
 987     /// A binary semaphore the submission also signals, for a waiter outside the stream.
 988     signal_semaphore: ?driver_mod.VkSemaphore = null,
 989 };
 990 
 991 /// Submits a recorded command buffer after the stream's previous work and every wait event, and
 992 /// advances the stream's timeline past it.
 993 pub fn submit(raster: *Raster, submission: Submission) Error!void {
 994     const stream = submission.stream;
 995     if (stream.poisoned) return Error.StreamPoisoned;
 996     if (submission.wait_events.len > max_wait_events) return Error.InitializationFailed;
 997     const rt = raster.runtime;
 998     const drv = &rt._driver;
 999     var waits: [max_wait_events + 1]driver_mod.VkSemaphoreSubmitInfo = undefined;
1000     waits[0] = semaphoreInfo(stream._semaphore, stream.counter);
1001     for (submission.wait_events, 1..) |event, index| {
1002         if (!event.recorded) return Error.InitializationFailed;
1003         waits[index] = semaphoreInfo(event._semaphore, event.value);
1004     }
1005     const signal_value = stream.counter + 1;
1006     var signals = [2]driver_mod.VkSemaphoreSubmitInfo{ semaphoreInfo(stream._semaphore, signal_value), undefined };
1007     if (submission.signal_semaphore) |semaphore| signals[1] = semaphoreInfo(semaphore, 0);
1008     const command = driver_mod.VkCommandBufferSubmitInfo{
1009         .sType = driver_mod.VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO,
1010         .pNext = null,
1011         .commandBuffer = submission.cb,
1012         .deviceMask = 0,
1013     };
1014     const info = driver_mod.VkSubmitInfo2{
1015         .sType = driver_mod.VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
1016         .pNext = null,
1017         .flags = 0,
1018         .waitSemaphoreInfoCount = @intCast(1 + submission.wait_events.len),
1019         .pWaitSemaphoreInfos = @ptrCast(&waits),
1020         .commandBufferInfoCount = 1,
1021         .pCommandBufferInfos = @ptrCast(&command),
1022         .signalSemaphoreInfoCount = if (submission.signal_semaphore != null) 2 else 1,
1023         .pSignalSemaphoreInfos = @ptrCast(&signals),
1024     };
1025     drv.fromResult(drv.vkQueueSubmit2(rt._queue, 1, @ptrCast(&info), null)) catch |err| {
1026         stream.poisoned = true;
1027         return err;
1028     };
1029     raster.pending = true;
1030     if (submission.retire) runtime_mod.retireStreamCommandBuffer(stream, submission.cb, signal_value);
1031     stream.counter = signal_value;
1032     if (submission.signal_event) |event| {
1033         event._semaphore = stream._semaphore;
1034         event.value = signal_value;
1035         event.recorded = true;
1036     }
1037 }
1038 
1039 fn semaphoreInfo(semaphore: driver_mod.VkSemaphore, value: u64) driver_mod.VkSemaphoreSubmitInfo {
1040     return .{
1041         .sType = driver_mod.VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
1042         .pNext = null,
1043         .semaphore = semaphore,
1044         .value = value,
1045         .stageMask = pipeline_stage_2_all_commands,
1046         .deviceIndex = 0,
1047     };
1048 }
1049 
1050 test "home layouts put sampling ahead of attachment use" {
1051     const sampled_color = driver_mod.VK_IMAGE_USAGE_SAMPLED_BIT | driver_mod.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1052     try std.testing.expectEqual(driver_mod.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, homeLayout(sampled_color));
1053     try std.testing.expectEqual(color_layout, homeLayout(driver_mod.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT));
1054     try std.testing.expectEqual(depth_layout, homeLayout(driver_mod.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT));
1055     try std.testing.expectEqual(driver_mod.VK_IMAGE_LAYOUT_GENERAL, homeLayout(driver_mod.VK_IMAGE_USAGE_TRANSFER_SRC_BIT));
1056 }
1057 
1058 test "blend states spell the contract's equations" {
1059     const one = driver_mod.VK_BLEND_FACTOR_ONE;
1060     const inverse_alpha = driver_mod.VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
1061     try std.testing.expectEqual(driver_mod.VK_FALSE, blendState(.replace).blendEnable);
1062     const premultiplied = blendState(.alpha_premultiplied);
1063     try std.testing.expectEqual(one, premultiplied.srcColorBlendFactor);
1064     try std.testing.expectEqual(inverse_alpha, premultiplied.dstColorBlendFactor);
1065     try std.testing.expectEqual(one, premultiplied.srcAlphaBlendFactor);
1066     try std.testing.expectEqual(inverse_alpha, premultiplied.dstAlphaBlendFactor);
1067     const straight = blendState(.alpha_straight);
1068     try std.testing.expectEqual(driver_mod.VK_BLEND_FACTOR_SRC_ALPHA, straight.srcColorBlendFactor);
1069     try std.testing.expectEqual(inverse_alpha, straight.dstColorBlendFactor);
1070     try std.testing.expectEqual(one, straight.srcAlphaBlendFactor);
1071     try std.testing.expectEqual(inverse_alpha, straight.dstAlphaBlendFactor);
1072     const additive = blendState(.additive);
1073     try std.testing.expectEqual(one, additive.dstColorBlendFactor);
1074     try std.testing.expectEqual(one, additive.dstAlphaBlendFactor);
1075     for ([_]Blend{ .alpha_premultiplied, .alpha_straight, .additive }) |blend| {
1076         try std.testing.expectEqual(driver_mod.VK_TRUE, blendState(blend).blendEnable);
1077         try std.testing.expectEqual(driver_mod.VK_BLEND_OP_ADD, blendState(blend).colorBlendOp);
1078     }
1079 }
1080 
1081 test "granular padding keeps images off buffer pages" {
1082     const padded = granular(.{ .size = 100, .alignment = 16, .memoryTypeBits = 3 }, 1024);
1083     try std.testing.expectEqual(@as(u64, 1024), padded.size);
1084     try std.testing.expectEqual(@as(u64, 1024), padded.alignment);
1085     try std.testing.expectEqual(@as(u32, 3), padded.memoryTypeBits);
1086     const unchanged = granular(.{ .size = 4096, .alignment = 4096, .memoryTypeBits = 1 }, 1);
1087     try std.testing.expectEqual(@as(u64, 4096), unchanged.size);
1088     try std.testing.expectEqual(@as(u64, 4096), unchanged.alignment);
1089 }