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(®ion)),
444 .download => drv.vkCmdCopyImageToBuffer(cb, self.image, transfer_layout, buffer, 1, @ptrCast(®ion)),
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 }