lib/gpu/src/vulkan.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const choir_abi = @import("choir_abi");
   3 const build_options = @import("build_options");
   4 const sys = @import("sys");
   5 
   6 const backend = @import("root.zig");
   7 const runtime_root = @import("runtime/root.zig");
   8 
   9 const launch_mod = runtime_root.vulkan.launch;
  10 const raster_mod = runtime_root.vulkan.raster;
  11 const dmabuf_mod = runtime_root.vulkan.dmabuf;
  12 const memory_mod = runtime_root.vulkan.memory;
  13 const runtime_mod = runtime_root.vulkan.runtime;
  14 const driver_mod = sys.vulkan;
  15 
  16 const Allocator = std.mem.Allocator;
  17 const BackendObjectId = backend.BackendObjectId;
  18 const Runtime = runtime_mod.Runtime;
  19 
  20 const RuntimeStorage = union(enum) {
  21     none,
  22     borrowed: *Runtime,
  23     owned: Runtime,
  24 
  25     fn ptr(self: *RuntimeStorage) ?*Runtime {
  26         return switch (self.*) {
  27             .none => null,
  28             .borrowed => |runtime| runtime,
  29             .owned => |*runtime| runtime,
  30         };
  31     }
  32 
  33     fn deinit(self: *RuntimeStorage) void {
  34         switch (self.*) {
  35             .none, .borrowed => {},
  36             .owned => |*runtime| runtime.deinit(),
  37         }
  38         self.* = .none;
  39     }
  40 };
  41 
  42 /// The raster state beside a runtime, made the first time a texture, pipeline or capability query
  43 /// needs it. A runtime whose queue runs no graphics leaves it unavailable.
  44 const RasterSlot = union(enum) {
  45     untried,
  46     unavailable,
  47     ready: raster_mod.Raster,
  48 };
  49 
  50 pub const State = struct {
  51     allocator: Allocator,
  52     runtime: RuntimeStorage = .none,
  53     next_id: BackendObjectId = 1,
  54     objects: std.AutoHashMapUnmanaged(BackendObjectId, Object) = .{},
  55     raster: RasterSlot = .untried,
  56     /// A device's capabilities never change, so the first query answers every later one.
  57     caps: ?backend.BackendCapabilities = null,
  58     /// The dma-buf target whose frame is being recorded, the only time its images may be named.
  59     presenting: ?BackendObjectId = null,
  60 
  61     fn init(allocator: Allocator) State {
  62         return .{
  63             .allocator = allocator,
  64         };
  65     }
  66 
  67     pub fn initDevice(allocator: Allocator, device_ordinal: i32) backend.BackendError!State {
  68         var diagnostic: []const u8 = "none";
  69         return initDeviceDiagnosed(allocator, device_ordinal, &diagnostic);
  70     }
  71 
  72     pub fn initDeviceDiagnosed(allocator: Allocator, device_ordinal: i32, diagnostic: *[]const u8) backend.BackendError!State {
  73         const runtime = Runtime.init(allocator, device_ordinal) catch |err| {
  74             diagnostic.* = @errorName(err);
  75             return mapRuntimeError(err);
  76         };
  77         diagnostic.* = "none";
  78         return .{
  79             .allocator = allocator,
  80             .runtime = .{ .owned = runtime },
  81         };
  82     }
  83 
  84     fn initWithRuntime(allocator: Allocator, runtime: ?*Runtime) State {
  85         return .{
  86             .allocator = allocator,
  87             .runtime = if (runtime) |rt| .{ .borrowed = rt } else .none,
  88         };
  89     }
  90 
  91     pub fn deinit(self: *State) void {
  92         var it = self.objects.iterator();
  93         while (it.next()) |entry| {
  94             deinitObject(self, entry.value_ptr);
  95         }
  96         self.objects.deinit(self.allocator);
  97         self.objects = .{};
  98         self.next_id = 1;
  99         switch (self.raster) {
 100             .ready => |*raster| raster.deinit(),
 101             .untried, .unavailable => {},
 102         }
 103         self.raster = .untried;
 104         self.caps = null;
 105         self.runtime.deinit();
 106     }
 107 
 108     fn rasterState(self: *State) backend.BackendError!*raster_mod.Raster {
 109         switch (self.raster) {
 110             .ready => |*raster| return raster,
 111             .unavailable => return error.CapabilityMismatch,
 112             .untried => {},
 113         }
 114         const rt = self.runtime.ptr() orelse return error.RuntimeUnavailable;
 115         const raster = raster_mod.Raster.init(rt) catch |err| switch (err) {
 116             error.FeatureNotPresent => {
 117                 self.raster = .unavailable;
 118                 return error.CapabilityMismatch;
 119             },
 120             else => return mapRasterError(err, error.RuntimeUnavailable),
 121         };
 122         self.raster = .{ .ready = raster };
 123         return &self.raster.ready;
 124     }
 125 
 126     /// The raster that made an existing raster object.
 127     fn liveRaster(self: *State) *raster_mod.Raster {
 128         return &self.raster.ready;
 129     }
 130 
 131     pub fn handle(self: *State) backend.BackendHandle {
 132         return .{
 133             .ptr = self,
 134             .vtable = &vtable,
 135             .kind = .vulkan,
 136         };
 137     }
 138 
 139     fn putObject(self: *State, object: Object) backend.BackendError!BackendObjectId {
 140         const id = self.next_id;
 141         if (id == std.math.maxInt(BackendObjectId)) return error.OutOfMemory;
 142         self.next_id += 1;
 143         self.objects.put(self.allocator, id, object) catch return error.OutOfMemory;
 144         return id;
 145     }
 146 
 147     fn getLoaded(self: *State, loaded: backend.LoadedArtifact) backend.BackendError!*LoadedKernel {
 148         if (loaded.backend != .vulkan or loaded.format != .vulkan_spirv) return error.InvalidArtifact;
 149         const object = self.objects.getPtr(loaded.id) orelse return error.InvalidArtifact;
 150         return switch (object.*) {
 151             .loaded_artifact => |*kernel| kernel,
 152             else => error.InvalidArtifact,
 153         };
 154     }
 155 
 156     fn getBuffer(self: *State, buffer_handle: backend.BufferHandle) backend.BackendError!*runtime_mod.DeviceBuffer {
 157         if (buffer_handle.backend != .vulkan) return error.InvalidBuffer;
 158         const object = self.objects.getPtr(buffer_handle.id) orelse return error.InvalidBuffer;
 159         return switch (object.*) {
 160             .loaded_artifact => error.InvalidBuffer,
 161             .buffer => |*buffer| buffer,
 162             else => error.InvalidBuffer,
 163         };
 164     }
 165 
 166     fn getStream(self: *State, stream_handle: backend.StreamHandle) backend.BackendError!*runtime_mod.Stream {
 167         if (stream_handle.backend != .vulkan) return error.InvalidStream;
 168         const object = self.objects.getPtr(stream_handle.id) orelse return error.InvalidStream;
 169         return switch (object.*) {
 170             .stream => |stream| stream,
 171             else => error.InvalidStream,
 172         };
 173     }
 174 
 175     fn getEvent(self: *State, event_handle: backend.EventHandle) backend.BackendError!*runtime_mod.Event {
 176         if (event_handle.backend != .vulkan) return error.InvalidEvent;
 177         const object = self.objects.getPtr(event_handle.id) orelse return error.InvalidEvent;
 178         return switch (object.*) {
 179             .event => |event| event,
 180             else => error.InvalidEvent,
 181         };
 182     }
 183 
 184     fn getSurface(self: *State, surface_handle: backend.SurfaceHandle) backend.BackendError!*VulkanSurface {
 185         if (surface_handle.backend != .vulkan) return error.InvalidSurface;
 186         const object = self.objects.getPtr(surface_handle.id) orelse return error.InvalidSurface;
 187         return switch (object.*) {
 188             .surface => |*surface| surface,
 189             else => error.InvalidSurface,
 190         };
 191     }
 192 
 193     fn getTexture(self: *State, texture_handle: backend.TextureHandle) backend.BackendError!*VulkanTexture {
 194         if (texture_handle.backend != .vulkan) return error.InvalidTexture;
 195         const object = self.objects.getPtr(texture_handle.id) orelse return error.InvalidTexture;
 196         return switch (object.*) {
 197             .texture => |*texture| texture,
 198             else => error.InvalidTexture,
 199         };
 200     }
 201 
 202     /// An image the contract may name now. An image a dma-buf target shares with another process
 203     /// is named only by its target's frame, since between frames that process owns it.
 204     fn getImage(self: *State, texture_handle: backend.TextureHandle) backend.BackendError!*VulkanImage {
 205         const image = try self.getAnyImage(texture_handle);
 206         if (image.target) |owner| if (self.presenting != owner) return error.InvalidTexture;
 207         return image;
 208     }
 209 
 210     fn getAnyImage(self: *State, texture_handle: backend.TextureHandle) backend.BackendError!*VulkanImage {
 211         if (texture_handle.backend != .vulkan) return error.InvalidTexture;
 212         const object = self.objects.getPtr(texture_handle.id) orelse return error.InvalidTexture;
 213         return switch (object.*) {
 214             .image => |*image| if (std.meta.eql(image.handle, texture_handle)) image else error.InvalidTexture,
 215             else => error.InvalidTexture,
 216         };
 217     }
 218 
 219     fn getRenderPipeline(self: *State, loaded: backend.LoadedRenderArtifact) backend.BackendError!*VulkanPipeline {
 220         if (loaded.backend != .vulkan) return error.InvalidRenderArtifact;
 221         const object = self.objects.getPtr(loaded.id) orelse return error.InvalidRenderArtifact;
 222         return switch (object.*) {
 223             .render_pipeline => |*pipeline| if (std.meta.eql(pipeline.loaded, loaded)) pipeline else error.InvalidRenderArtifact,
 224             else => error.InvalidRenderArtifact,
 225         };
 226     }
 227 
 228     fn getRenderBindings(self: *State, bindings: backend.RenderBindings) backend.BackendError!*VulkanBindings {
 229         if (bindings.backend != .vulkan) return error.RenderArgumentMismatch;
 230         const object = self.objects.getPtr(bindings.id) orelse return error.RenderArgumentMismatch;
 231         return switch (object.*) {
 232             .render_bindings => |*set| if (set.pipeline_id == bindings.pipeline) set else error.RenderArgumentMismatch,
 233             else => error.RenderArgumentMismatch,
 234         };
 235     }
 236 
 237     fn getRenderBundle(self: *State, bundle: backend.RenderBundle) backend.BackendError!*VulkanBundle {
 238         if (bundle.backend != .vulkan) return error.RenderArgumentMismatch;
 239         const object = self.objects.getPtr(bundle.id) orelse return error.RenderArgumentMismatch;
 240         return switch (object.*) {
 241             .render_bundle => |*recorded| if (recorded.draw_count == bundle.draw_count) recorded else error.RenderArgumentMismatch,
 242             else => error.RenderArgumentMismatch,
 243         };
 244     }
 245 
 246     fn submitStream(self: *State, stream_handle: ?backend.StreamHandle) backend.BackendError!*runtime_mod.Stream {
 247         if (stream_handle) |handle_value| return self.getStream(handle_value);
 248         const rt = self.runtime.ptr() orelse return error.RuntimeUnavailable;
 249         return rt.defaultStream() catch |err| mapRuntimeError(err);
 250     }
 251 
 252     fn submitWaits(
 253         self: *State,
 254         handles: []const backend.EventHandle,
 255         events: *[raster_mod.max_wait_events]*runtime_mod.Event,
 256     ) backend.BackendError![]const *runtime_mod.Event {
 257         if (handles.len > raster_mod.max_wait_events) return error.RenderArgumentMismatch;
 258         for (handles, 0..) |event_handle, index| {
 259             const event = try self.getEvent(event_handle);
 260             if (!event.recorded) return error.InvalidEvent;
 261             events[index] = event;
 262         }
 263         return events[0..handles.len];
 264     }
 265 
 266     /// Where the device placed a texture and a buffer, and which queue runs them. A witness reports
 267     /// it so a run on another driver says what that driver chose.
 268     pub fn placement(self: *State, texture: backend.TextureHandle, buffer: backend.BufferHandle) backend.BackendError!Placement {
 269         const raster = try self.rasterState();
 270         const rt = raster.runtime;
 271         const image = try self.getImage(texture);
 272         const device_buffer = try self.getBuffer(buffer);
 273         return .{
 274             .queue_family = rt.device.queueFamilyIndex(),
 275             .graphics = rt.device.graphics(),
 276             .buffer_image_granularity = raster.buffer_image_granularity,
 277             .image_memory = switch (image.image.backing) {
 278                 .pooled => |allocation| memoryPlacement(rt, allocation),
 279                 .dedicated => return error.InvalidTexture,
 280             },
 281             .buffer_memory = memoryPlacement(rt, device_buffer._allocation),
 282         };
 283     }
 284 
 285     /// Records `pass`'s targets and one bare draw command per draw into a command buffer it then
 286     /// frees unsubmitted. It binds the first draw's pipeline, bindings and vertex buffers once and
 287     /// checks nothing per draw, so its time is the least any path recording these draws on this
 288     /// driver can take. The raster witness measures the contract's recording against it.
 289     pub fn recordFloor(self: *State, pass: backend.RenderPass) backend.BackendError!void {
 290         if (pass.draws.len == 0) return error.RenderArgumentMismatch;
 291         const raster = try self.rasterState();
 292         const drv = &raster.runtime._driver;
 293         const cb = raster_mod.beginBundle(raster) catch |err| return mapRasterError(err, error.RenderFailed);
 294         defer raster.discardBundle(cb);
 295         const targets = try passTargets(self, pass);
 296         const first = pass.draws[0];
 297         const pipeline = try self.getRenderPipeline(first.pipeline);
 298         raster_mod.beginPass(drv, cb, targets);
 299         drv.vkCmdBindPipeline(cb, driver_mod.VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline.pipeline);
 300         if (first.bindings) |bindings| {
 301             const set = try self.getRenderBindings(bindings);
 302             drv.vkCmdBindDescriptorSets(cb, driver_mod.VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline.layout, 0, 1, @ptrCast(&set.set.set), 0, null);
 303         }
 304         for (first.vertex_buffers, pipeline.layouts[0..first.vertex_buffers.len]) |range, layout| {
 305             const buffer = try self.getBuffer(range.buffer);
 306             drv.vkCmdBindVertexBuffers(cb, layout.binding, 1, @ptrCast(&buffer._buffer), @ptrCast(&range.offset));
 307         }
 308         for (pass.draws) |draw| {
 309             if (draw.push_constants.len != 0) {
 310                 const size: u32 = @intCast(draw.push_constants.len);
 311                 drv.vkCmdPushConstants(cb, pipeline.pipeline.layout, raster_mod.push_constant_stages, 0, size, draw.push_constants.ptr);
 312             }
 313             drv.vkCmdDraw(cb, draw.range.vertex_count, draw.range.instance_count, draw.range.first_vertex, draw.range.first_instance);
 314         }
 315         raster_mod.endPass(drv, cb, targets);
 316         drv.fromResult(drv.vkEndCommandBuffer(cb)) catch |err| return mapRasterError(err, error.RenderFailed);
 317     }
 318 
 319     /// Makes a ring of color images another process on `desc.device` can import as dma-bufs, each
 320     /// registered as a texture a frame's pass renders into. See `runtime/vulkan/dmabuf.zig` for the
 321     /// frame protocol.
 322     pub fn createDmabufTarget(self: *State, desc: DmabufTargetDesc) DmabufError!BackendObjectId {
 323         const raster = try self.rasterState();
 324         if (desc.format.isDepth()) return error.CapabilityMismatch;
 325         if (desc.image_count > dmabuf_mod.max_images) return error.CapabilityMismatch;
 326         const id = self.next_id;
 327         if (id > std.math.maxInt(BackendObjectId) - 1 - @as(BackendObjectId, desc.image_count)) return error.OutOfMemory;
 328         try self.objects.ensureUnusedCapacity(self.allocator, 1 + desc.image_count);
 329         const shared = try self.allocator.create(VulkanDmabufTarget);
 330         errdefer self.allocator.destroy(shared);
 331         var images: [dmabuf_mod.max_images]raster_mod.Image = undefined;
 332         shared.* = .{
 333             .target = dmabuf_mod.Target.create(raster, .{
 334                 .width = desc.width,
 335                 .height = desc.height,
 336                 .format = try vulkanFormat(desc.format),
 337                 .modifiers = desc.modifiers,
 338                 .image_count = desc.image_count,
 339                 .device = desc.device,
 340             }, &images) catch |err| return mapDmabufError(err),
 341             .textures = undefined,
 342         };
 343         self.next_id += 1 + desc.image_count;
 344         self.objects.putAssumeCapacity(id, .{ .dmabuf_target = shared });
 345         for (images[0..desc.image_count], shared.textures[0..desc.image_count], 1..) |image, *texture, offset| {
 346             texture.* = .{
 347                 .id = id + @as(BackendObjectId, @intCast(offset)),
 348                 .backend = .vulkan,
 349                 .extent = .{ .width = desc.width, .height = desc.height, .depth = 1 },
 350                 .format = desc.format,
 351                 .usage = .{ .color_attachment = true },
 352                 .sample_count = 1,
 353                 .ownership = .backend,
 354             };
 355             self.objects.putAssumeCapacity(texture.id, .{ .image = .{ .handle = texture.*, .image = image, .target = id } });
 356         }
 357         return id;
 358     }
 359 
 360     /// Waits up to a second for the consumer to release every image, then frees the target and
 361     /// its textures.
 362     pub fn destroyDmabufTarget(self: *State, id: BackendObjectId) DmabufError!void {
 363         const shared = try self.getDmabufTarget(id);
 364         if (shared.target.open != null) return error.RenderArgumentMismatch;
 365         const count = shared.target.count;
 366         const textures = shared.textures;
 367         var target = self.objects.fetchRemove(id).?.value;
 368         deinitObject(self, &target);
 369         for (textures[0..count]) |texture| {
 370             var image = self.objects.fetchRemove(texture.id).?.value;
 371             deinitObject(self, &image);
 372         }
 373     }
 374 
 375     /// Image `index` of a target: its texture, its dma-buf and its timeline's descriptor. The
 376     /// target owns both descriptors.
 377     pub fn dmabufImage(self: *State, id: BackendObjectId, index: u32) DmabufError!DmabufImage {
 378         const shared = try self.getDmabufTarget(id);
 379         if (index >= shared.target.count) return error.InvalidTexture;
 380         return .{
 381             .texture = shared.textures[index],
 382             .dmabuf = shared.target.dmabuf(index),
 383             .timeline_fd = shared.target.timelineFd(index),
 384         };
 385     }
 386 
 387     pub fn dmabufImageCount(self: *State, id: BackendObjectId) DmabufError!u32 {
 388         return (try self.getDmabufTarget(id)).target.count;
 389     }
 390 
 391     /// The highest signalled point on image `index`'s timeline.
 392     pub fn dmabufSignalledPoint(self: *State, id: BackendObjectId, index: u32) DmabufError!u64 {
 393         const shared = try self.getDmabufTarget(id);
 394         if (index >= shared.target.count) return error.InvalidTexture;
 395         return shared.target.signalledPoint(index) catch |err| mapDmabufError(err);
 396     }
 397 
 398     /// Picks the target's next image, waiting until `deadline_ns` on `CLOCK_MONOTONIC` for the
 399     /// consumer to release it. The frame's pass must render into the returned texture.
 400     pub fn beginDmabufFrame(self: *State, id: BackendObjectId, deadline_ns: i64) DmabufError!DmabufFrame {
 401         const shared = try self.getDmabufTarget(id);
 402         if (shared.broken) return error.RenderFailed;
 403         if (shared.target.open != null) return error.RenderArgumentMismatch;
 404         const frame = shared.target.begin(deadline_ns) catch |err| return mapDmabufError(err);
 405         return .{
 406             .index = frame.index,
 407             .texture = shared.textures[frame.index],
 408             .acquire_point = frame.acquire_point,
 409             .release_point = frame.release_point,
 410         };
 411     }
 412 
 413     /// Records `pass` between the barriers that take the frame's image from the consumer and hand
 414     /// it back, submits it on the default stream, and attaches its completion to the frame's
 415     /// acquire point. A failure before the submission queues leaves the frame open to try again.
 416     pub fn submitDmabufFrame(self: *State, id: BackendObjectId, frame: DmabufFrame, pass: backend.RenderPass) DmabufError!void {
 417         const raster = try self.rasterState();
 418         const shared = try self.getDmabufTarget(id);
 419         const inner = dmabuf_mod.Frame{ .index = frame.index, .acquire_point = frame.acquire_point, .release_point = frame.release_point };
 420         if (!std.meta.eql(shared.target.open, inner)) return error.RenderArgumentMismatch;
 421         if (!std.meta.eql(pass.color.view.texture, shared.textures[frame.index])) return error.RenderArgumentMismatch;
 422         const stream = raster.runtime.defaultStream() catch |err| return mapRuntimeError(err);
 423         const cb = raster_mod.beginStreamPass(stream) catch |err| return mapRasterError(err, error.RenderFailed);
 424         self.presenting = id;
 425         defer self.presenting = null;
 426         shared.target.recordAcquire(cb, inner);
 427         recordPass(self, raster, cb, pass) catch |err| {
 428             runtime_mod.retireStreamCommandBuffer(stream, cb, stream.counter);
 429             return err;
 430         };
 431         shared.target.recordRelease(cb, inner);
 432         const drv = &raster.runtime._driver;
 433         drv.fromResult(drv.vkEndCommandBuffer(cb)) catch |err| {
 434             runtime_mod.retireStreamCommandBuffer(stream, cb, stream.counter);
 435             return mapRasterError(err, error.RenderFailed);
 436         };
 437         raster_mod.submit(raster, .{
 438             .stream = stream,
 439             .cb = cb,
 440             .wait_events = &.{},
 441             .signal_event = null,
 442             .retire = true,
 443             .signal_semaphore = shared.target.semaphore,
 444         }) catch |err| return mapRasterError(err, error.RenderFailed);
 445         shared.target.finish(inner) catch |err| {
 446             shared.broken = true;
 447             return mapDmabufError(err);
 448         };
 449     }
 450 
 451     /// Signals the release point of a submitted frame the consumer never received, such as one a
 452     /// window refused, so the image can be used again.
 453     pub fn reclaimDmabufFrame(self: *State, id: BackendObjectId, frame: DmabufFrame) DmabufError!void {
 454         const shared = try self.getDmabufTarget(id);
 455         if (frame.index >= shared.target.count) return error.RenderArgumentMismatch;
 456         if (frame.release_point != 2 * shared.target.slots[frame.index].uses) return error.RenderArgumentMismatch;
 457         shared.target.reclaim(.{ .index = frame.index, .acquire_point = frame.acquire_point, .release_point = frame.release_point }) catch |err|
 458             return mapDmabufError(err);
 459     }
 460 
 461     /// Submits an empty command buffer that signals the target's semaphore, and moves its fence
 462     /// onto a scratch timeline as a frame's would be. It is the least a frame's submission and
 463     /// sync bridge can cost on this driver, and a witness measures `submitDmabufFrame` against it.
 464     pub fn dmabufBridgeFloor(self: *State, id: BackendObjectId) DmabufError!void {
 465         const raster = try self.rasterState();
 466         const shared = try self.getDmabufTarget(id);
 467         if (shared.broken or shared.target.open != null) return error.RenderArgumentMismatch;
 468         const stream = raster.runtime.defaultStream() catch |err| return mapRuntimeError(err);
 469         const cb = raster_mod.beginStreamPass(stream) catch |err| return mapRasterError(err, error.RenderFailed);
 470         const drv = &raster.runtime._driver;
 471         drv.fromResult(drv.vkEndCommandBuffer(cb)) catch |err| {
 472             runtime_mod.retireStreamCommandBuffer(stream, cb, stream.counter);
 473             return mapRasterError(err, error.RenderFailed);
 474         };
 475         raster_mod.submit(raster, .{
 476             .stream = stream,
 477             .cb = cb,
 478             .wait_events = &.{},
 479             .signal_event = null,
 480             .retire = true,
 481             .signal_semaphore = shared.target.semaphore,
 482         }) catch |err| return mapRasterError(err, error.RenderFailed);
 483         shared.target.bridgeScratch() catch |err| {
 484             shared.broken = true;
 485             return mapDmabufError(err);
 486         };
 487     }
 488 
 489     fn getDmabufTarget(self: *State, id: BackendObjectId) backend.BackendError!*VulkanDmabufTarget {
 490         const object = self.objects.getPtr(id) orelse return error.InvalidTexture;
 491         return switch (object.*) {
 492             .dmabuf_target => |shared| shared,
 493             else => error.InvalidTexture,
 494         };
 495     }
 496 
 497     fn getFrame(self: *State, frame_handle: backend.SurfaceFrame) backend.BackendError!*VulkanSurfaceFrame {
 498         if (frame_handle.backend != .vulkan) return error.InvalidSurfaceFrame;
 499         const object = self.objects.getPtr(frame_handle.id) orelse return error.InvalidSurfaceFrame;
 500         return switch (object.*) {
 501             .surface_frame => |*frame| frame,
 502             else => error.InvalidSurfaceFrame,
 503         };
 504     }
 505 };
 506 
 507 pub const DmabufError = backend.BackendError || error{
 508     /// The device did not enable dma-buf export or cannot name its render node.
 509     NoDmabufDevice,
 510     /// The consumer wants buffers from another device.
 511     DeviceMismatch,
 512     /// The device can render the format with none of the modifiers the consumer lists.
 513     NoSharedModifier,
 514     /// The render node would not open for this process.
 515     RenderNodeUnavailable,
 516     /// The consumer had not released the image by the deadline.
 517     Timeout,
 518 };
 519 
 520 pub const DmabufTargetDesc = struct {
 521     width: u32,
 522     height: u32,
 523     format: backend.TextureFormat,
 524     /// Modifiers of the format the consumer can import, such as a compositor's dma-buf feedback
 525     /// lists for its main device.
 526     modifiers: []const u64,
 527     image_count: u32,
 528     device: sys.drm.DeviceNumber,
 529 };
 530 
 531 pub const DmabufImage = struct {
 532     texture: backend.TextureHandle,
 533     dmabuf: dmabuf_mod.Dmabuf,
 534     timeline_fd: i32,
 535 };
 536 
 537 /// One image's turn. The consumer may read the image once `acquire_point` on its timeline
 538 /// signals, and signals `release_point` when it is done.
 539 pub const DmabufFrame = struct {
 540     index: u32,
 541     texture: backend.TextureHandle,
 542     acquire_point: u64,
 543     release_point: u64,
 544 };
 545 
 546 fn mapDmabufError(err: dmabuf_mod.Error) DmabufError {
 547     return switch (err) {
 548         error.NoDmabufDevice, error.UnsupportedPlatform, error.Unsupported => error.NoDmabufDevice,
 549         error.DeviceMismatch => error.DeviceMismatch,
 550         error.NoSharedModifier => error.NoSharedModifier,
 551         error.OpenFailed, error.AccessDenied => error.RenderNodeUnavailable,
 552         error.Timeout => error.Timeout,
 553         error.OutOfMemory, error.OutOfHostMemory, error.OutOfDeviceMemory => error.OutOfMemory,
 554         error.DeviceLost => error.DeviceLost,
 555         error.InvalidHandle, error.Failed => error.RenderFailed,
 556         else => |vulkan_err| mapRasterError(vulkan_err, error.RenderFailed),
 557     };
 558 }
 559 
 560 /// A memory type by index, with its property flags and the size of the heap it draws on.
 561 pub const MemoryPlacement = struct {
 562     type_index: u32,
 563     property_flags: driver_mod.VkMemoryPropertyFlags,
 564     heap_bytes: u64,
 565 };
 566 
 567 pub const Placement = struct {
 568     queue_family: u32,
 569     graphics: bool,
 570     buffer_image_granularity: u64,
 571     image_memory: MemoryPlacement,
 572     buffer_memory: MemoryPlacement,
 573 };
 574 
 575 fn memoryPlacement(rt: *runtime_mod.Runtime, allocation: memory_mod.Allocation) MemoryPlacement {
 576     const index = rt._memory.memoryTypeIndex(allocation);
 577     const memory_type = rt._memory.properties.memoryTypes[index];
 578     return .{
 579         .type_index = index,
 580         .property_flags = memory_type.propertyFlags,
 581         .heap_bytes = rt._memory.properties.memoryHeaps[memory_type.heapIndex].size,
 582     };
 583 }
 584 
 585 const LoadedKernel = struct {
 586     kernel: launch_mod.Kernel,
 587     entry_name: [:0]u8,
 588     buffer_argument_count: u32,
 589     scalar_argument_count: u32,
 590     push_constants: choir_abi.PushConstants,
 591 };
 592 
 593 const Object = union(enum) {
 594     loaded_artifact: LoadedKernel,
 595     buffer: runtime_mod.DeviceBuffer,
 596     stream: *runtime_mod.Stream,
 597     event: *runtime_mod.Event,
 598     surface: VulkanSurface,
 599     texture: VulkanTexture,
 600     surface_frame: VulkanSurfaceFrame,
 601     image: VulkanImage,
 602     render_pipeline: VulkanPipeline,
 603     render_bindings: VulkanBindings,
 604     render_bundle: VulkanBundle,
 605     dmabuf_target: *VulkanDmabufTarget,
 606 };
 607 
 608 /// A texture this backend allocated, as opposed to a swapchain image a surface lent it.
 609 const VulkanImage = struct {
 610     handle: backend.TextureHandle,
 611     image: raster_mod.Image,
 612     /// The dma-buf target that shares the image, if one does.
 613     target: ?BackendObjectId = null,
 614 };
 615 
 616 const VulkanDmabufTarget = struct {
 617     target: dmabuf_mod.Target,
 618     textures: [dmabuf_mod.max_images]backend.TextureHandle,
 619     /// A frame whose submission queued but whose fence never reached the timeline. The target's
 620     /// semaphore may still be signalled, so the target takes no more frames.
 621     broken: bool = false,
 622 };
 623 
 624 const VertexLayout = struct {
 625     binding: u32,
 626     stride: u32,
 627     per_instance: bool,
 628 };
 629 
 630 /// A graphics pipeline and the facts its draws are checked against: the loaded description, the
 631 /// Vulkan binding each vertex layout feeds, and the pipeline's resource bindings in order.
 632 const VulkanPipeline = struct {
 633     pipeline: raster_mod.Pipeline,
 634     loaded: backend.LoadedRenderArtifact,
 635     layouts: [raster_mod.max_vertex_buffers]VertexLayout,
 636     bindings: [raster_mod.max_descriptors]backend.RenderBindingDesc,
 637 };
 638 
 639 const VulkanBindings = struct {
 640     set: raster_mod.BindingSet,
 641     pipeline_id: BackendObjectId,
 642 };
 643 
 644 const VulkanBundle = struct {
 645     command_buffer: driver_mod.VkCommandBuffer,
 646     draw_count: u32,
 647 };
 648 
 649 const VulkanSurface = struct {
 650     surface: runtime_mod.Surface,
 651     handle: backend.SurfaceHandle,
 652     acquired_frame: ?BackendObjectId = null,
 653     acquired_texture: ?BackendObjectId = null,
 654 };
 655 
 656 const VulkanTexture = struct {
 657     handle: backend.TextureHandle,
 658     image: runtime_mod.SurfaceImage,
 659 };
 660 
 661 const VulkanSurfaceFrame = struct {
 662     surface_id: BackendObjectId,
 663     texture_id: BackendObjectId,
 664     image_index: u32,
 665     generation: u64,
 666     written: bool = false,
 667     presented: bool = false,
 668 };
 669 
 670 pub fn staticCapabilities() backend.BackendCapabilities {
 671     return capabilitiesFrom(null, false);
 672 }
 673 
 674 fn capabilitiesFrom(caps: ?runtime_mod.Runtime.Caps, has_runtime: bool) backend.BackendCapabilities {
 675     var dtypes = backend.DTypeSet.init(&.{ .i32, .u32, .f32 });
 676     if (caps) |actual| {
 677         if (actual.storage_buffer8) dtypes.insert(.i1);
 678         if (actual.shader_float16 and actual.storage_buffer16) dtypes.insert(.f16);
 679         if (actual.shader_float64) dtypes.insert(.f64);
 680         if (actual.shader_int8 and actual.storage_buffer8) {
 681             dtypes.insert(.i8);
 682             dtypes.insert(.u8);
 683         }
 684         if (actual.shader_int16 and actual.storage_buffer16) {
 685             dtypes.insert(.i16);
 686             dtypes.insert(.u16);
 687         }
 688         if (actual.shader_int64) {
 689             dtypes.insert(.i64);
 690             dtypes.insert(.u64);
 691         }
 692     } else {
 693         dtypes.insert(.i1);
 694     }
 695 
 696     const subgroup_size_reported = if (caps) |actual| actual.subgroup_size else 0;
 697     const subgroup_stages = if (caps) |actual| actual.subgroup_supported_stages else 0;
 698     const subgroup_operations = if (caps) |actual| actual.subgroup_supported_operations else 0;
 699     const subgroup_supported = subgroup_size_reported != 0 and
 700         hasAllBits(subgroup_stages, driver_mod.VK_SHADER_STAGE_COMPUTE_BIT) and
 701         hasAllBits(subgroup_operations, driver_mod.VK_SUBGROUP_FEATURE_BASIC_BIT);
 702     const subgroup_size = if (subgroup_supported) subgroup_size_reported else 0;
 703     return .{
 704         .identity = .{
 705             .backend = .vulkan,
 706             .family = .vulkan,
 707             .name = if (caps) |actual| actual.vendor_name else "vulkan",
 708             .vendor_id = if (caps) |actual| actual.vendor_id else null,
 709         },
 710         .memory = .{
 711             .min_buffer_alignment = 16,
 712             .host_visible_device_memory = true,
 713         },
 714         .subgroup = .{
 715             .supported = subgroup_supported,
 716             .size_min = subgroup_size,
 717             .size_max = subgroup_size,
 718             .shuffle = subgroup_supported and hasAllBits(
 719                 subgroup_operations,
 720                 driver_mod.VK_SUBGROUP_FEATURE_SHUFFLE_BIT |
 721                     driver_mod.VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT,
 722             ),
 723             .ballot = subgroup_supported and
 724                 hasAllBits(subgroup_operations, driver_mod.VK_SUBGROUP_FEATURE_BALLOT_BIT),
 725             .vote = subgroup_supported and
 726                 hasAllBits(subgroup_operations, driver_mod.VK_SUBGROUP_FEATURE_VOTE_BIT),
 727             .arithmetic = subgroup_supported and
 728                 hasAllBits(subgroup_operations, driver_mod.VK_SUBGROUP_FEATURE_ARITHMETIC_BIT),
 729             .scan = subgroup_supported and
 730                 hasAllBits(subgroup_operations, driver_mod.VK_SUBGROUP_FEATURE_ARITHMETIC_BIT),
 731         },
 732         .threadgroup = .{
 733             .max_threads = 1024,
 734             .max_blocks = .{ 65_535, 65_535, 65_535 },
 735             .max_threads_per_dim = .{ 1024, 1024, 64 },
 736             .max_grid_per_dim = .{ 65_535, 65_535, 65_535 },
 737         },
 738         .dtypes = dtypes,
 739         .layouts = .{
 740             .row_major = true,
 741             .compact_strides = true,
 742             .broadcast_strides = true,
 743             .tiled = true,
 744             .opaque_backend_layouts = true,
 745         },
 746         .runtime = .{
 747             .driver_loaded = has_runtime,
 748             .device_context = has_runtime,
 749             .streams = true,
 750             .events = true,
 751             .timeline_events = true,
 752         },
 753         .features = .{
 754             .atomic_i32 = true,
 755             .atomic_u32 = true,
 756             .atomic_index = true,
 757         },
 758         .artifact_formats = backend.ArtifactFormatSet.init(&.{.vulkan_spirv}),
 759         .surfaces = .{
 760             .supported = has_runtime,
 761             .platforms = backend.SurfacePlatformSet.init(&.{.x11}),
 762             .formats = backend.TextureFormatSet.init(&.{ .rgba8_unorm, .bgra8_unorm }),
 763             .color_spaces = backend.ColorSpaceSet.init(&.{.srgb}),
 764             .present_modes = backend.PresentModeSet.init(&.{ .fifo, .mailbox, .immediate }),
 765             .usages = .{
 766                 .copy_src = true,
 767                 .copy_dst = true,
 768                 .color_attachment = true,
 769                 .present = true,
 770             },
 771             .max_extent = .{ .width = 16_384, .height = 16_384 },
 772             .max_frames_in_flight = 4,
 773         },
 774     };
 775 }
 776 
 777 fn hasAllBits(actual: u32, required: u32) bool {
 778     return actual & required == required;
 779 }
 780 
 781 fn queryCapabilities(ptr: *anyopaque) backend.BackendError!backend.BackendCapabilities {
 782     const state: *State = @ptrCast(@alignCast(ptr));
 783     if (state.caps) |caps| return caps;
 784     const rt = state.runtime.ptr() orelse return staticCapabilities();
 785     const runtime_caps = rt.queryCapabilities() catch |err| return mapRuntimeError(err);
 786     var caps = capabilitiesFrom(runtime_caps, true);
 787     caps.float_controls = queryFloatControls(rt);
 788     if (state.rasterState()) |raster| {
 789         addRasterCapabilities(&caps, raster);
 790     } else |err| switch (err) {
 791         error.CapabilityMismatch => {},
 792         else => return err,
 793     }
 794     state.caps = caps;
 795     return caps;
 796 }
 797 
 798 /// VkPhysicalDeviceFloatControlsProperties is a Vulkan 1.2 properties-chain node.
 799 const FloatProperties = extern struct {
 800     sType: driver_mod.VkStructureType = 1000197000,
 801     pNext: ?*anyopaque = null,
 802     denormBehaviorIndependence: u32 = 0,
 803     roundingModeIndependence: u32 = 0,
 804     shaderSignedZeroInfNanPreserveFloat16: driver_mod.VkBool32 = 0,
 805     shaderSignedZeroInfNanPreserveFloat32: driver_mod.VkBool32 = 0,
 806     shaderSignedZeroInfNanPreserveFloat64: driver_mod.VkBool32 = 0,
 807     shaderDenormPreserveFloat16: driver_mod.VkBool32 = 0,
 808     shaderDenormPreserveFloat32: driver_mod.VkBool32 = 0,
 809     shaderDenormPreserveFloat64: driver_mod.VkBool32 = 0,
 810     shaderDenormFlushToZeroFloat16: driver_mod.VkBool32 = 0,
 811     shaderDenormFlushToZeroFloat32: driver_mod.VkBool32 = 0,
 812     shaderDenormFlushToZeroFloat64: driver_mod.VkBool32 = 0,
 813     shaderRoundingModeRTEFloat16: driver_mod.VkBool32 = 0,
 814     shaderRoundingModeRTEFloat32: driver_mod.VkBool32 = 0,
 815     shaderRoundingModeRTEFloat64: driver_mod.VkBool32 = 0,
 816     shaderRoundingModeRTZFloat16: driver_mod.VkBool32 = 0,
 817     shaderRoundingModeRTZFloat32: driver_mod.VkBool32 = 0,
 818     shaderRoundingModeRTZFloat64: driver_mod.VkBool32 = 0,
 819 };
 820 
 821 fn queryFloatControls(rt: *Runtime) backend.FloatControlFacts {
 822     var float_props = FloatProperties{};
 823     var props = driver_mod.VkPhysicalDeviceProperties2{
 824         .sType = driver_mod.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
 825         .pNext = @ptrCast(&float_props),
 826         .properties = undefined,
 827     };
 828     rt._driver.vkGetPhysicalDeviceProperties2(rt._physical_device, &props);
 829     return floatFactsFrom(float_props);
 830 }
 831 
 832 fn floatFactsFrom(float_props: FloatProperties) backend.FloatControlFacts {
 833     return .{
 834         .denorm_preserve = .{
 835             .f16 = float_props.shaderDenormPreserveFloat16 == driver_mod.VK_TRUE,
 836             .f32 = float_props.shaderDenormPreserveFloat32 == driver_mod.VK_TRUE,
 837             .f64 = float_props.shaderDenormPreserveFloat64 == driver_mod.VK_TRUE,
 838         },
 839         .signed_zero_inf_nan_preserve = .{
 840             .f16 = float_props.shaderSignedZeroInfNanPreserveFloat16 == driver_mod.VK_TRUE,
 841             .f32 = float_props.shaderSignedZeroInfNanPreserveFloat32 == driver_mod.VK_TRUE,
 842             .f64 = float_props.shaderSignedZeroInfNanPreserveFloat64 == driver_mod.VK_TRUE,
 843         },
 844         .denorm_behavior_independence = switch (float_props.denormBehaviorIndependence) {
 845             0 => .bit32_only,
 846             1 => .all,
 847             2 => .none,
 848             else => null,
 849         },
 850     };
 851 }
 852 
 853 test "vulkan float controls report independent support for each width" {
 854     var props = FloatProperties{};
 855     props.denormBehaviorIndependence = @backingInt(backend.FloatControlIndependence.bit32_only);
 856     props.shaderDenormPreserveFloat32 = driver_mod.VK_TRUE;
 857     props.shaderSignedZeroInfNanPreserveFloat16 = driver_mod.VK_TRUE;
 858     const facts = floatFactsFrom(props);
 859     try std.testing.expectEqual(@as(?backend.FloatControlIndependence, .bit32_only), facts.denorm_behavior_independence);
 860     try std.testing.expect(facts.denorm_preserve.f32);
 861     try std.testing.expect(!facts.denorm_preserve.f16);
 862     try std.testing.expect(facts.signed_zero_inf_nan_preserve.f16);
 863     try std.testing.expect(!facts.signed_zero_inf_nan_preserve.f64);
 864     props.denormBehaviorIndependence = 32;
 865     try std.testing.expectEqual(null, floatFactsFrom(props).denorm_behavior_independence);
 866 }
 867 
 868 const color_formats = [_]backend.TextureFormat{ .rgba8_unorm, .bgra8_unorm, .rgba8_srgb, .bgra8_srgb };
 869 
 870 /// Every feature a color texture needs for any usage the textures report: sampling with either
 871 /// filter, blended attachment writes, and both transfer directions.
 872 const color_features = driver_mod.VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
 873     driver_mod.VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |
 874     driver_mod.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
 875     driver_mod.VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT |
 876     driver_mod.VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
 877     driver_mod.VK_FORMAT_FEATURE_TRANSFER_DST_BIT;
 878 
 879 const depth_features = driver_mod.VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
 880     driver_mod.VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT |
 881     driver_mod.VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
 882     driver_mod.VK_FORMAT_FEATURE_TRANSFER_DST_BIT;
 883 
 884 /// Reports textures and rasterization for the formats whose every needed feature the device
 885 /// reports, so a texture of a reported format serves every reported usage its aspect allows.
 886 fn addRasterCapabilities(caps: *backend.BackendCapabilities, raster: *const raster_mod.Raster) void {
 887     var targets: backend.TextureFormatSet = .{};
 888     for (color_formats) |format| {
 889         const vk_format = vulkanFormat(format) catch unreachable;
 890         if (hasAllBits(raster.formatFeatures(vk_format), color_features)) targets.insert(format);
 891     }
 892     var depths: backend.TextureFormatSet = .{};
 893     if (hasAllBits(raster.formatFeatures(driver_mod.VK_FORMAT_D32_SFLOAT), depth_features)) depths.insert(.depth32_float);
 894     var formats = targets;
 895     formats.bits |= depths.bits;
 896     caps.textures = .{
 897         .supported = true,
 898         .formats = formats,
 899         .usages = .{
 900             .copy_src = true,
 901             .copy_dst = true,
 902             .sampled = true,
 903             .color_attachment = true,
 904             .depth_attachment = true,
 905         },
 906         .max_extent = .{ .width = raster.max_image_extent, .height = raster.max_image_extent, .depth = 1 },
 907         .max_sample_count = 1,
 908     };
 909     caps.raster = .{
 910         .supported = true,
 911         .artifact_formats = backend.RenderArtifactFormatSet.init(&.{.vulkan_spirv}),
 912         .target_formats = targets,
 913         .depth_formats = depths,
 914         .blend_modes = backend.RenderBlendModeSet.init(&.{ .replace, .alpha_premultiplied, .alpha_straight, .additive }),
 915         .topologies = backend.RenderPrimitiveTopologySet.init(&.{ .triangle_list, .triangle_strip, .line_list, .line_strip }),
 916         .vertex_formats = backend.RenderVertexFormatSet.init(&.{ .float32, .float32x2, .float32x3, .float32x4, .uint32, .uint32x2, .uint32x4 }),
 917         .binding_kinds = backend.RenderBindingKindSet.init(&.{ .uniform_buffer, .sampled_texture }),
 918         .index_formats = backend.RenderIndexFormatSet.init(&.{ .none, .u16, .u32 }),
 919         .max_vertex_buffers = raster.max_vertex_buffers,
 920         .max_vertex_attributes = raster.max_vertex_attributes,
 921         .max_bindings = raster_mod.max_descriptors,
 922         .instancing = true,
 923         .max_push_constant_bytes = raster.max_push_constant_bytes,
 924         .depth_bias = true,
 925         .depth_bias_clamp = raster.runtime._depth_bias_clamp,
 926     };
 927 }
 928 
 929 fn createArtifact(ptr: *anyopaque, request: backend.CompileRequest) backend.BackendError!backend.KernelArtifact {
 930     if (request.requested_format != .vulkan_spirv) return error.UnsupportedOperation;
 931     const state: *State = @ptrCast(@alignCast(ptr));
 932     return switch (request.payload) {
 933         .words_u32 => |words| createSpirvArtifact(state, request, words),
 934         .none => error.UnsupportedOperation,
 935         else => error.UnsupportedOperation,
 936     };
 937 }
 938 
 939 fn createSpirvArtifact(
 940     state: *State,
 941     request: backend.CompileRequest,
 942     words: []const u32,
 943 ) backend.BackendError!backend.KernelArtifact {
 944     if (request.kernel_name.len == 0) return error.InvalidArtifact;
 945     if (words.len == 0) return error.InvalidArtifact;
 946 
 947     var artifact = backend.KernelArtifact.init(state.allocator, .{
 948         .backend = .vulkan,
 949         .format = .vulkan_spirv,
 950         .entry_name = request.kernel_name,
 951         .argument_count = request.argument_count,
 952         .scalar_argument_count = request.scalar_argument_count,
 953         .diagnostic_id = request.diagnostic_id,
 954     }) catch return error.OutOfMemory;
 955     errdefer artifact.deinit();
 956     try artifact.setOwnedWords(words);
 957     return artifact;
 958 }
 959 
 960 fn loadArtifact(ptr: *anyopaque, artifact: *const backend.KernelArtifact) backend.BackendError!backend.LoadedArtifact {
 961     const state: *State = @ptrCast(@alignCast(ptr));
 962     if (artifact.backend != .vulkan) return error.CapabilityMismatch;
 963     if (artifact.format != .vulkan_spirv) return error.UnsupportedArtifactFormat;
 964     if (artifact.entry_name.len == 0) return error.InvalidArtifact;
 965     const buffer_argument_count = try artifact.bufferArgumentCount();
 966     if (buffer_argument_count > runtime_mod.max_buffers_per_set) return error.InvalidArtifact;
 967 
 968     const words = switch (artifact.payload) {
 969         .words_u32 => |words| words,
 970         else => return error.InvalidArtifact,
 971     };
 972     if (words.len == 0) return error.InvalidArtifact;
 973 
 974     const rt = state.runtime.ptr() orelse return error.RuntimeUnavailable;
 975     const entry_name = state.allocator.dupeSentinel(u8, artifact.entry_name, 0) catch return error.OutOfMemory;
 976     errdefer state.allocator.free(entry_name);
 977 
 978     const push_constants = artifact.interface.push_constants;
 979     if (push_constants.count != artifact.scalar_argument_count) return error.InvalidArtifact;
 980     const push_constant_size: u32 = push_constants.byte_size;
 981     var kernel = launch_mod.compile(rt, words, entry_name, buffer_argument_count, push_constant_size) catch |err| return mapArtifactError(err);
 982     errdefer kernel.deinit();
 983 
 984     const id = try state.putObject(.{ .loaded_artifact = .{
 985         .kernel = kernel,
 986         .entry_name = entry_name,
 987         .buffer_argument_count = buffer_argument_count,
 988         .scalar_argument_count = artifact.scalar_argument_count,
 989         .push_constants = push_constants,
 990     } });
 991     return .{
 992         .id = id,
 993         .backend = .vulkan,
 994         .format = .vulkan_spirv,
 995     };
 996 }
 997 
 998 fn allocateBuffer(ptr: *anyopaque, request: backend.BufferAllocation) backend.BackendError!backend.BufferHandle {
 999     const state: *State = @ptrCast(@alignCast(ptr));
1000     if (request.byte_size == 0) return error.InvalidBuffer;
1001     const rt = state.runtime.ptr() orelse return error.RuntimeUnavailable;
1002 
1003     var buffer = runtime_mod.DeviceBuffer.alloc(rt, request.byte_size) catch |err| return mapAllocationError(err);
1004     errdefer buffer.deinit();
1005 
1006     const id = try state.putObject(.{ .buffer = buffer });
1007     return .{
1008         .id = id,
1009         .backend = .vulkan,
1010         .byte_size = request.byte_size,
1011         .ownership = .backend,
1012     };
1013 }
1014 
1015 fn createSurface(ptr: *anyopaque, request: backend.SurfaceCreationRequest) backend.BackendError!backend.SurfaceHandle {
1016     const state: *State = @ptrCast(@alignCast(ptr));
1017     const rt = state.runtime.ptr() orelse return error.RuntimeUnavailable;
1018     const desc = try surfaceDescFromRequest(request);
1019 
1020     var surface = rt.createXlibSurface(desc) catch |err| return mapSurfaceRuntimeError(err);
1021     errdefer surface.deinit();
1022 
1023     const handle = backend.SurfaceHandle{
1024         .id = 0,
1025         .backend = .vulkan,
1026         .platform = request.platform.kind(),
1027         .extent = .{ .width = surface.extent.width, .height = surface.extent.height },
1028         .format = request.format,
1029         .color_space = request.color_space,
1030         .present_mode = request.present_mode,
1031         .generation = surface.generation,
1032     };
1033     const id = try state.putObject(.{ .surface = .{
1034         .surface = surface,
1035         .handle = handle,
1036     } });
1037     const object = state.objects.getPtr(id).?;
1038     switch (object.*) {
1039         .surface => |*stored| stored.handle.id = id,
1040         else => unreachable,
1041     }
1042     return switch (object.*) {
1043         .surface => |*stored| stored.handle,
1044         else => unreachable,
1045     };
1046 }
1047 
1048 fn destroySurface(ptr: *anyopaque, handle: backend.SurfaceHandle) backend.BackendError!void {
1049     const state: *State = @ptrCast(@alignCast(ptr));
1050     const surface = try state.getSurface(handle);
1051     if (surface.handle.generation != handle.generation) return error.SurfaceFrameExpired;
1052     if (surface.acquired_frame != null) return error.SurfaceAlreadyAcquired;
1053     const entry = state.objects.fetchRemove(handle.id) orelse return error.InvalidSurface;
1054     var object = entry.value;
1055     deinitObject(state, &object);
1056 }
1057 
1058 fn destroyTexture(ptr: *anyopaque, handle: backend.TextureHandle) backend.BackendError!void {
1059     const state: *State = @ptrCast(@alignCast(ptr));
1060     if (state.getAnyImage(handle)) |image| {
1061         if (image.target != null) return error.InvalidTexture;
1062         const entry = state.objects.fetchRemove(handle.id).?;
1063         var object = entry.value;
1064         deinitObject(state, &object);
1065         return;
1066     } else |_| {}
1067     _ = try state.getTexture(handle);
1068     var it = state.objects.iterator();
1069     while (it.next()) |entry| {
1070         switch (entry.value_ptr.*) {
1071             .surface => |*surface| {
1072                 if (surface.acquired_texture == handle.id) return error.SurfaceAlreadyAcquired;
1073             },
1074             else => {},
1075         }
1076     }
1077     const entry = state.objects.fetchRemove(handle.id) orelse return error.InvalidTexture;
1078     var object = entry.value;
1079     deinitObject(state, &object);
1080 }
1081 
1082 fn acquireSurfaceFrame(
1083     ptr: *anyopaque,
1084     request: backend.SurfaceFrameAcquireRequest,
1085 ) backend.BackendError!backend.SurfaceFrame {
1086     const state: *State = @ptrCast(@alignCast(ptr));
1087     try state.objects.ensureUnusedCapacity(state.allocator, 2);
1088     const surface = try state.getSurface(request.surface);
1089     if (surface.handle.generation != request.surface.generation) return error.SurfaceFrameExpired;
1090     if (surface.acquired_frame != null) return error.SurfaceAlreadyAcquired;
1091 
1092     const texture_id = state.next_id;
1093     if (texture_id == std.math.maxInt(BackendObjectId)) return error.OutOfMemory;
1094     const frame_id = texture_id + 1;
1095     if (frame_id == std.math.maxInt(BackendObjectId)) return error.OutOfMemory;
1096     state.next_id = frame_id + 1;
1097 
1098     const image = surface.surface.acquire() catch |err| return mapSurfaceRuntimeError(err);
1099 
1100     const texture = backend.TextureHandle{
1101         .id = texture_id,
1102         .backend = .vulkan,
1103         .extent = .{
1104             .width = image.extent.width,
1105             .height = image.extent.height,
1106             .depth = 1,
1107         },
1108         .format = request.surface.format,
1109         .usage = textureUsageFromImageUsage(image.usage),
1110         .sample_count = 1,
1111         .ownership = .acquired_surface,
1112     };
1113     const frame_surface = surface.handle;
1114     const view = backend.TextureView{
1115         .texture = texture,
1116         .format = texture.format,
1117     };
1118     const frame = backend.SurfaceFrame{
1119         .id = frame_id,
1120         .backend = .vulkan,
1121         .surface = frame_surface,
1122         .texture = texture,
1123         .view = view,
1124         .index = image.image_index,
1125         .generation = image.generation,
1126         .token = image.image,
1127     };
1128 
1129     surface.acquired_frame = frame_id;
1130     surface.acquired_texture = texture_id;
1131     state.objects.putAssumeCapacityNoClobber(texture_id, .{ .texture = .{
1132         .handle = texture,
1133         .image = image,
1134     } });
1135     state.objects.putAssumeCapacityNoClobber(frame_id, .{ .surface_frame = .{
1136         .surface_id = request.surface.id,
1137         .texture_id = texture_id,
1138         .image_index = image.image_index,
1139         .generation = image.generation,
1140     } });
1141     return frame;
1142 }
1143 
1144 fn presentSurfaceFrame(ptr: *anyopaque, request: backend.PresentRequest) backend.BackendError!void {
1145     const state: *State = @ptrCast(@alignCast(ptr));
1146     if (request.signal_event != null) return error.UnsupportedOperation;
1147     const surface = try state.getSurface(request.surface);
1148     const frame = try state.getFrame(request.frame);
1149     if (surface.handle.generation != request.surface.generation) return error.SurfaceFrameExpired;
1150     if (frame.generation != request.frame.generation) return error.SurfaceFrameExpired;
1151     if (frame.presented) return error.SurfaceFrameExpired;
1152     if (frame.surface_id != request.surface.id) return error.InvalidSurfaceFrame;
1153     if (frame.texture_id != request.frame.texture.id) return error.InvalidSurfaceFrame;
1154     if (surface.acquired_frame == null or surface.acquired_frame.? != request.frame.id) return error.InvalidSurfaceFrame;
1155 
1156     for (request.wait_events) |event_handle| {
1157         const event = try state.getEvent(event_handle);
1158         if (!event.recorded) return error.InvalidEvent;
1159         event.synchronize() catch |err| return mapRuntimeError(err);
1160     }
1161 
1162     surface.surface.present(frame.image_index, frame.generation) catch |err| return mapSurfaceRuntimeError(err);
1163     frame.presented = true;
1164     surface.acquired_frame = null;
1165     surface.acquired_texture = null;
1166 }
1167 
1168 fn writeSurfaceFrame(ptr: *anyopaque, request: backend.SurfaceFrameWriteRequest) backend.BackendError!void {
1169     const state: *State = @ptrCast(@alignCast(ptr));
1170     const surface = try state.getSurface(request.surface);
1171     const frame = try state.getFrame(request.frame);
1172     if (surface.handle.generation != request.surface.generation) return error.SurfaceFrameExpired;
1173     if (frame.generation != request.frame.generation) return error.SurfaceFrameExpired;
1174     if (frame.presented or frame.written) return error.SurfaceFrameExpired;
1175     if (frame.surface_id != request.surface.id) return error.InvalidSurfaceFrame;
1176     if (frame.texture_id != request.frame.texture.id) return error.InvalidSurfaceFrame;
1177     if (surface.acquired_frame == null or surface.acquired_frame.? != request.frame.id) return error.InvalidSurfaceFrame;
1178     const texture = try state.getTexture(request.frame.texture);
1179 
1180     if (request.wait_events.len > runtime_mod.max_surface_wait_events) return error.LaunchArgumentMismatch;
1181     var wait_events: [runtime_mod.max_surface_wait_events]*runtime_mod.Event = undefined;
1182     for (request.wait_events, 0..) |event_handle, index| {
1183         const event = try state.getEvent(event_handle);
1184         if (!event.recorded) return error.InvalidEvent;
1185         wait_events[index] = event;
1186     }
1187     const signal_event = if (request.signal_event) |event_handle| try state.getEvent(event_handle) else null;
1188 
1189     const ops = state.allocator.alloc(runtime_mod.SurfaceWriteOp, request.operations.len) catch return error.OutOfMemory;
1190     defer state.allocator.free(ops);
1191     for (request.operations, 0..) |op, index| {
1192         ops[index] = switch (op) {
1193             .clear => |color| .{ .clear = .{ .r = color.r, .g = color.g, .b = color.b, .a = color.a } },
1194             .copy_buffer => |buffer_handle| .{ .copy_buffer = try state.getBuffer(buffer_handle) },
1195         };
1196     }
1197     surface.surface.write(texture.image, ops, wait_events[0..request.wait_events.len], signal_event) catch |err| return mapSurfaceRuntimeError(err);
1198     frame.written = true;
1199 }
1200 
1201 fn createStream(ptr: *anyopaque, _: backend.StreamAllocation) backend.BackendError!backend.StreamHandle {
1202     const state: *State = @ptrCast(@alignCast(ptr));
1203     const rt = state.runtime.ptr() orelse return error.RuntimeUnavailable;
1204 
1205     const stream = rt.createStream() catch |err| return mapRuntimeError(err);
1206     errdefer rt.destroyStream(stream);
1207 
1208     const id = try state.putObject(.{ .stream = stream });
1209     return .{
1210         .id = id,
1211         .backend = .vulkan,
1212     };
1213 }
1214 
1215 fn createEvent(ptr: *anyopaque, _: backend.EventAllocation) backend.BackendError!backend.EventHandle {
1216     const state: *State = @ptrCast(@alignCast(ptr));
1217     const rt = state.runtime.ptr() orelse return error.RuntimeUnavailable;
1218 
1219     const event = rt.createEvent() catch |err| return mapRuntimeError(err);
1220     errdefer rt.destroyEvent(event);
1221 
1222     const id = try state.putObject(.{ .event = event });
1223     return .{
1224         .id = id,
1225         .backend = .vulkan,
1226     };
1227 }
1228 
1229 fn writeBuffer(ptr: *anyopaque, request: backend.BufferWriteRequest) backend.BackendError!void {
1230     const state: *State = @ptrCast(@alignCast(ptr));
1231     const buffer = try state.getBuffer(request.handle);
1232     if (request.bytes.len > buffer.size) return error.InvalidBuffer;
1233     buffer.copyFromHost(request.bytes) catch |err| return mapAllocationError(err);
1234 }
1235 
1236 fn readBuffer(ptr: *anyopaque, request: backend.BufferReadRequest) backend.BackendError!void {
1237     const state: *State = @ptrCast(@alignCast(ptr));
1238     const buffer = try state.getBuffer(request.handle);
1239     if (request.bytes.len < buffer.size) return error.ReadBufferDestinationTooSmall;
1240     buffer.copyToHost(request.bytes) catch |err| return mapAllocationError(err);
1241 }
1242 
1243 fn launch(ptr: *anyopaque, request: backend.LaunchRequest) backend.BackendError!void {
1244     const state: *State = @ptrCast(@alignCast(ptr));
1245     if (request.geometry.dynamic_shared_memory_bytes != 0) return error.UnsupportedOperation;
1246     if (request.artifact.backend != .vulkan or request.artifact.format != .vulkan_spirv) {
1247         return error.CapabilityMismatch;
1248     }
1249     const rt = state.runtime.ptr() orelse return error.RuntimeUnavailable;
1250     const loaded_handle = request.loaded_artifact orelse return error.InvalidArtifact;
1251     const loaded = try state.getLoaded(loaded_handle);
1252     if (request.buffers.len != @as(usize, @intCast(loaded.buffer_argument_count))) {
1253         return error.LaunchArgumentMismatch;
1254     }
1255     if (request.scalar_arguments.len != @as(usize, @intCast(loaded.scalar_argument_count))) {
1256         return error.LaunchArgumentMismatch;
1257     }
1258     if (request.buffers.len > runtime_mod.max_buffers_per_set) return error.LaunchArgumentMismatch;
1259 
1260     var push_storage: [choir_abi.max_push_constant_bytes]u8 = undefined;
1261     const push_size = try loaded.push_constants.pack(request.scalar_arguments, &push_storage);
1262 
1263     var args: [runtime_mod.max_buffers_per_set]*runtime_mod.DeviceBuffer = undefined;
1264     for (request.buffers, 0..) |binding, i| {
1265         if (binding.handle.backend != .vulkan or binding.ownership != .backend) return error.InvalidBuffer;
1266         const buffer = try state.getBuffer(binding.handle);
1267         if (binding.byte_size > buffer.size or binding.handle.byte_size != buffer.size) {
1268             return error.InvalidBuffer;
1269         }
1270         args[i] = buffer;
1271     }
1272 
1273     const stream = if (request.stream) |stream_handle|
1274         try state.getStream(stream_handle)
1275     else
1276         rt.defaultStream() catch |err| return mapRuntimeError(err);
1277 
1278     if (request.wait_events.len > launch_mod.max_wait_events) return error.LaunchArgumentMismatch;
1279     var wait_events: [launch_mod.max_wait_events]*runtime_mod.Event = undefined;
1280     for (request.wait_events, 0..) |event_handle, i| {
1281         const event = try state.getEvent(event_handle);
1282         if (!event.recorded) return error.InvalidEvent;
1283         wait_events[i] = event;
1284     }
1285 
1286     const signal_event = if (request.signal_event) |event_handle|
1287         try state.getEvent(event_handle)
1288     else
1289         null;
1290 
1291     loaded.kernel.launch(
1292         stream,
1293         args[0..request.buffers.len],
1294         push_storage[0..push_size],
1295         request.geometry.grid,
1296         request.geometry.threadgroup,
1297         wait_events[0..request.wait_events.len],
1298         signal_event,
1299     ) catch |err| return mapLaunchError(err);
1300 }
1301 
1302 fn synchronize(ptr: *anyopaque, request: backend.SyncRequest) backend.BackendError!void {
1303     const state: *State = @ptrCast(@alignCast(ptr));
1304     switch (request.scope) {
1305         .default_stream => {
1306             const rt = state.runtime.ptr() orelse return error.RuntimeUnavailable;
1307             const stream = rt.defaultStream() catch |err| return mapRuntimeError(err);
1308             stream.synchronize() catch |err| return mapRuntimeError(err);
1309         },
1310         .device => {
1311             const rt = state.runtime.ptr() orelse return error.RuntimeUnavailable;
1312             rt.synchronize() catch |err| return mapRuntimeError(err);
1313         },
1314         .stream => {
1315             const stream = try state.getStream(request.stream.?);
1316             stream.synchronize() catch |err| return mapRuntimeError(err);
1317         },
1318         .event => {
1319             const event = try state.getEvent(request.event.?);
1320             if (!event.recorded) return error.InvalidEvent;
1321             event.synchronize() catch |err| return mapRuntimeError(err);
1322         },
1323     }
1324 }
1325 
1326 fn queryEvent(ptr: *anyopaque, request: backend.EventQueryRequest) backend.BackendError!bool {
1327     const state: *State = @ptrCast(@alignCast(ptr));
1328     const event = try state.getEvent(request.event);
1329     if (!event.recorded) return false;
1330     return event.query() catch |err| return mapRuntimeError(err);
1331 }
1332 
1333 fn recordEvent(ptr: *anyopaque, request: backend.EventRecordRequest) backend.BackendError!void {
1334     const state: *State = @ptrCast(@alignCast(ptr));
1335     const stream = try state.getStream(request.stream);
1336     const event = try state.getEvent(request.event);
1337     event.record(stream) catch |err| return mapRuntimeError(err);
1338 }
1339 
1340 fn elapsedEventNs(ptr: *anyopaque, request: backend.EventElapsedRequest) backend.BackendError!u64 {
1341     const state: *State = @ptrCast(@alignCast(ptr));
1342     const start = try state.getEvent(request.start);
1343     const end = try state.getEvent(request.end);
1344     return end.elapsedNs(start) catch |err| return mapElapsedEventError(err);
1345 }
1346 
1347 fn destroyObject(ptr: *anyopaque, id: BackendObjectId) void {
1348     const state: *State = @ptrCast(@alignCast(ptr));
1349     if (state.objects.fetchRemove(id)) |entry| {
1350         var object = entry.value;
1351         deinitObject(state, &object);
1352     }
1353 }
1354 
1355 fn deinitHandle(ptr: *anyopaque, allocator: Allocator) void {
1356     _ = allocator;
1357     const state: *State = @ptrCast(@alignCast(ptr));
1358     state.deinit();
1359 }
1360 
1361 fn deinitObject(state: *State, object: *Object) void {
1362     switch (object.*) {
1363         .loaded_artifact => |*loaded| {
1364             loaded.kernel.deinit();
1365             state.allocator.free(loaded.entry_name);
1366         },
1367         .image => |*texture| texture.image.destroy(state.liveRaster()),
1368         .render_pipeline => |*pipeline| pipeline.pipeline.destroy(state.liveRaster()),
1369         .render_bindings => |*bindings| bindings.set.destroy(state.liveRaster()),
1370         .render_bundle => |bundle| state.liveRaster().freeBundle(bundle.command_buffer),
1371         .dmabuf_target => |shared| {
1372             shared.target.destroy(state.liveRaster());
1373             state.allocator.destroy(shared);
1374         },
1375         .buffer => |*buffer| buffer.deinit(),
1376         .stream => |stream| stream._runtime.destroyStream(stream),
1377         .event => |event| event._runtime.destroyEvent(event),
1378         .surface => |*surface| surface.surface.deinit(),
1379         .texture, .surface_frame => {},
1380     }
1381     object.* = undefined;
1382 }
1383 
1384 fn allocateTexture(ptr: *anyopaque, request: backend.TextureAllocation) backend.BackendError!backend.TextureHandle {
1385     const state: *State = @ptrCast(@alignCast(ptr));
1386     const raster = try state.rasterState();
1387     if (request.sample_count != 1 or request.extent.depth != 1) return error.CapabilityMismatch;
1388     if (request.usage.present or request.usage.storage) return error.CapabilityMismatch;
1389     const depth = request.format.isDepth();
1390     if (depth and request.usage.color_attachment) return error.CapabilityMismatch;
1391     if (!depth and request.usage.depth_attachment) return error.CapabilityMismatch;
1392     try state.objects.ensureUnusedCapacity(state.allocator, 1);
1393 
1394     var image = raster_mod.Image.create(raster, .{
1395         .width = request.extent.width,
1396         .height = request.extent.height,
1397         .format = try vulkanFormat(request.format),
1398         .usage = imageUsageFromTextureUsage(request.usage),
1399         .aspect = if (depth) driver_mod.VK_IMAGE_ASPECT_DEPTH_BIT else driver_mod.VK_IMAGE_ASPECT_COLOR_BIT,
1400         .texel_bytes = request.format.texelBytes(),
1401     }) catch |err| return mapRasterError(err, error.InvalidTexture);
1402     errdefer image.destroy(raster);
1403 
1404     var texture = backend.TextureHandle{
1405         .id = 0,
1406         .backend = .vulkan,
1407         .extent = request.extent,
1408         .format = request.format,
1409         .usage = request.usage,
1410         .sample_count = 1,
1411         .ownership = .backend,
1412     };
1413     texture.id = try state.putObject(.{ .image = .{ .handle = texture, .image = image } });
1414     state.objects.getPtr(texture.id).?.image.handle.id = texture.id;
1415     return texture;
1416 }
1417 
1418 fn writeTexture(ptr: *anyopaque, request: backend.TextureWriteRequest) backend.BackendError!void {
1419     const state: *State = @ptrCast(@alignCast(ptr));
1420     const texture = try state.getImage(request.texture);
1421     texture.image.write(state.liveRaster(), request.bytes) catch |err| return mapRasterError(err, error.InvalidTexture);
1422 }
1423 
1424 fn readTexture(ptr: *anyopaque, request: backend.TextureReadRequest) backend.BackendError!void {
1425     const state: *State = @ptrCast(@alignCast(ptr));
1426     const texture = try state.getImage(request.texture);
1427     texture.image.read(state.liveRaster(), request.bytes) catch |err| return mapRasterError(err, error.InvalidTexture);
1428 }
1429 
1430 fn createRenderArtifact(ptr: *anyopaque, desc: backend.RenderPipelineDesc) backend.BackendError!backend.RenderArtifact {
1431     const state: *State = @ptrCast(@alignCast(ptr));
1432     if (desc.format != .vulkan_spirv) return error.UnsupportedArtifactFormat;
1433     const words = switch (desc.payload) {
1434         .words_u32 => |words| words,
1435         else => return error.InvalidRenderArtifact,
1436     };
1437     if (words.len == 0) return error.InvalidRenderArtifact;
1438     var artifact = backend.RenderArtifact.init(state.allocator, .{
1439         .backend = .vulkan,
1440         .pipeline = desc,
1441     }) catch return error.OutOfMemory;
1442     errdefer artifact.deinit();
1443     try artifact.setOwnedWords(words);
1444     return artifact;
1445 }
1446 
1447 /// Builds the pipeline an artifact describes. Every resource binding must sit in group 0, and each
1448 /// vertex layout's binding must be below the vertex buffer limit and used once.
1449 fn loadRenderArtifact(ptr: *anyopaque, artifact: *const backend.RenderArtifact) backend.BackendError!backend.LoadedRenderArtifact {
1450     const state: *State = @ptrCast(@alignCast(ptr));
1451     if (artifact.backend != .vulkan) return error.CapabilityMismatch;
1452     if (artifact.format != .vulkan_spirv) return error.UnsupportedArtifactFormat;
1453     const words = switch (artifact.payload) {
1454         .words_u32 => |words| words,
1455         else => return error.InvalidRenderArtifact,
1456     };
1457     if (words.len == 0) return error.InvalidRenderArtifact;
1458     const raster = try state.rasterState();
1459     if (artifact.vertex_layouts.len > raster.max_vertex_buffers) return error.CapabilityMismatch;
1460     if (artifact.bindings.len > raster_mod.max_descriptors) return error.CapabilityMismatch;
1461     try state.objects.ensureUnusedCapacity(state.allocator, 1);
1462 
1463     var object: VulkanPipeline = .{
1464         .pipeline = undefined,
1465         .loaded = undefined,
1466         .layouts = undefined,
1467         .bindings = undefined,
1468     };
1469     var vertex_bindings: [raster_mod.max_vertex_buffers]driver_mod.VkVertexInputBindingDescription = undefined;
1470     var vertex_attributes: [raster_mod.max_vertex_attributes]driver_mod.VkVertexInputAttributeDescription = undefined;
1471     var attribute_count: usize = 0;
1472     var used_vertex_bindings: u32 = 0;
1473     for (artifact.vertex_layouts, 0..) |layout, index| {
1474         if (layout.binding >= raster_mod.max_vertex_buffers) return error.CapabilityMismatch;
1475         const bit = @as(u32, 1) << @intCast(layout.binding);
1476         if (used_vertex_bindings & bit != 0) return error.InvalidRenderArtifact;
1477         used_vertex_bindings |= bit;
1478         const per_instance = layout.step_mode == .instance;
1479         vertex_bindings[index] = .{
1480             .binding = layout.binding,
1481             .stride = layout.stride,
1482             .inputRate = if (per_instance) driver_mod.VK_VERTEX_INPUT_RATE_INSTANCE else driver_mod.VK_VERTEX_INPUT_RATE_VERTEX,
1483         };
1484         object.layouts[index] = .{ .binding = layout.binding, .stride = layout.stride, .per_instance = per_instance };
1485         const start: usize = layout.attribute_start;
1486         const count: usize = layout.attribute_count;
1487         if (start > artifact.vertex_attributes.len or count > artifact.vertex_attributes.len - start) return error.InvalidRenderArtifact;
1488         for (artifact.vertex_attributes[start..][0..count]) |attribute| {
1489             if (attribute_count == raster.max_vertex_attributes) return error.CapabilityMismatch;
1490             vertex_attributes[attribute_count] = .{
1491                 .location = attribute.location,
1492                 .binding = layout.binding,
1493                 .format = vertexFormat(attribute.format),
1494                 .offset = attribute.offset,
1495             };
1496             attribute_count += 1;
1497         }
1498     }
1499     var descriptors: [raster_mod.max_descriptors]driver_mod.VkDescriptorSetLayoutBinding = undefined;
1500     var used_descriptor_bindings: u64 = 0;
1501     for (artifact.bindings, 0..) |binding, index| {
1502         if (binding.group != 0 or binding.binding >= 64) return error.CapabilityMismatch;
1503         const bit = @as(u64, 1) << @intCast(binding.binding);
1504         if (used_descriptor_bindings & bit != 0) return error.InvalidRenderArtifact;
1505         used_descriptor_bindings |= bit;
1506         descriptors[index] = .{
1507             .binding = binding.binding,
1508             .descriptorType = switch (binding.kind) {
1509                 .uniform_buffer => driver_mod.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
1510                 .sampled_texture => driver_mod.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1511                 .storage_buffer, .storage_texture => return error.CapabilityMismatch,
1512             },
1513             .descriptorCount = 1,
1514             .stageFlags = driver_mod.VK_SHADER_STAGE_VERTEX_BIT | driver_mod.VK_SHADER_STAGE_FRAGMENT_BIT,
1515             .pImmutableSamplers = null,
1516         };
1517         object.bindings[index] = binding;
1518     }
1519 
1520     const vertex_entry = state.allocator.dupeSentinel(u8, artifact.vertex_entry_name, 0) catch return error.OutOfMemory;
1521     defer state.allocator.free(vertex_entry);
1522     const fragment_entry = state.allocator.dupeSentinel(u8, artifact.fragment_entry_name, 0) catch return error.OutOfMemory;
1523     defer state.allocator.free(fragment_entry);
1524     const depth: ?raster_mod.DepthTest = if (artifact.depth) |depth_state| .{
1525         .format = try vulkanFormat(depth_state.format),
1526         .compare = vulkanCompare(depth_state.compare),
1527         .write = depth_state.write,
1528         .bias_constant = depth_state.bias.constant,
1529         .bias_slope = depth_state.bias.slope,
1530         .bias_clamp = depth_state.bias.clamp,
1531     } else null;
1532     if (artifact.push_constant_bytes > raster.max_push_constant_bytes) return error.CapabilityMismatch;
1533     if (depth) |test_state| {
1534         if (test_state.bias_clamp != 0 and !raster.runtime._depth_bias_clamp) return error.CapabilityMismatch;
1535     }
1536     object.pipeline = raster_mod.Pipeline.create(raster, .{
1537         .words = words,
1538         .vertex_entry = vertex_entry.ptr,
1539         .fragment_entry = fragment_entry.ptr,
1540         .color_format = try vulkanFormat(artifact.target_format),
1541         .depth = depth,
1542         .blend = switch (artifact.blend_mode) {
1543             .replace => .replace,
1544             .alpha_premultiplied => .alpha_premultiplied,
1545             .alpha_straight => .alpha_straight,
1546             .additive => .additive,
1547         },
1548         .topology = vulkanTopology(artifact.topology),
1549         .vertex_bindings = vertex_bindings[0..artifact.vertex_layouts.len],
1550         .vertex_attributes = vertex_attributes[0..attribute_count],
1551         .descriptors = descriptors[0..artifact.bindings.len],
1552         .push_constant_bytes = artifact.push_constant_bytes,
1553     }) catch |err| return mapRasterError(err, error.InvalidRenderArtifact);
1554     errdefer object.pipeline.destroy(raster);
1555 
1556     const id = try state.putObject(.{ .render_pipeline = object });
1557     const loaded = backend.LoadedRenderArtifact.describing(artifact, id);
1558     state.objects.getPtr(id).?.render_pipeline.loaded = loaded;
1559     return loaded;
1560 }
1561 
1562 fn createRenderBindings(ptr: *anyopaque, request: backend.RenderBindingsRequest) backend.BackendError!backend.RenderBindings {
1563     const state: *State = @ptrCast(@alignCast(ptr));
1564     const raster = try state.rasterState();
1565     try state.objects.ensureUnusedCapacity(state.allocator, 1);
1566     const pipeline = try state.getRenderPipeline(request.pipeline);
1567     const count: usize = pipeline.loaded.binding_count;
1568     if (request.resources.len != count) return error.RenderArgumentMismatch;
1569     var descriptors: [raster_mod.max_descriptors]raster_mod.Descriptor = undefined;
1570     for (request.resources, pipeline.bindings[0..count], 0..) |resource, binding, index| {
1571         if (std.meta.activeTag(resource) != binding.kind) return error.RenderArgumentMismatch;
1572         descriptors[index] = .{ .binding = binding.binding, .resource = switch (resource) {
1573             .uniform_buffer => |buffer_handle| .{ .buffer = .{
1574                 .buffer = (try state.getBuffer(buffer_handle))._buffer,
1575                 .kind = driver_mod.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
1576             } },
1577             .sampled_texture => |sampled| blk: {
1578                 const texture = try state.getImage(sampled.texture);
1579                 if (!texture.handle.usage.sampled) return error.InvalidTexture;
1580                 const sampler = raster.sampler(.{
1581                     .filter = switch (sampled.sampler.filter) {
1582                         .nearest => driver_mod.VK_FILTER_NEAREST,
1583                         .linear => driver_mod.VK_FILTER_LINEAR,
1584                     },
1585                     .address = switch (sampled.sampler.address) {
1586                         .clamp_to_edge => driver_mod.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
1587                         .repeat => driver_mod.VK_SAMPLER_ADDRESS_MODE_REPEAT,
1588                     },
1589                 }) catch |err| return mapRasterError(err, error.RenderFailed);
1590                 break :blk .{ .image = .{ .view = texture.image.view, .layout = texture.image.home, .sampler = sampler } };
1591             },
1592             .storage_buffer, .storage_texture => return error.CapabilityMismatch,
1593         } };
1594     }
1595     var set = raster_mod.BindingSet.create(raster, &pipeline.pipeline, descriptors[0..count]) catch |err| {
1596         return mapRasterError(err, error.RenderFailed);
1597     };
1598     errdefer set.destroy(raster);
1599     const id = try state.putObject(.{ .render_bindings = .{ .set = set, .pipeline_id = request.pipeline.id } });
1600     return .{ .id = id, .backend = .vulkan, .pipeline = request.pipeline.id };
1601 }
1602 
1603 fn render(ptr: *anyopaque, request: backend.RenderRequest) backend.BackendError!void {
1604     const state: *State = @ptrCast(@alignCast(ptr));
1605     const raster = try state.rasterState();
1606     const stream = try state.submitStream(request.stream);
1607     var wait_storage: [raster_mod.max_wait_events]*runtime_mod.Event = undefined;
1608     const waits = try state.submitWaits(request.wait_events, &wait_storage);
1609     const signal = if (request.signal_event) |event_handle| try state.getEvent(event_handle) else null;
1610 
1611     const cb = raster_mod.beginStreamPass(stream) catch |err| return mapRasterError(err, error.RenderFailed);
1612     recordPassAndEnd(state, raster, cb, request.pass) catch |err| {
1613         runtime_mod.retireStreamCommandBuffer(stream, cb, stream.counter);
1614         return err;
1615     };
1616     raster_mod.submit(raster, .{
1617         .stream = stream,
1618         .cb = cb,
1619         .wait_events = waits,
1620         .signal_event = signal,
1621         .retire = true,
1622     }) catch |err| return mapRasterError(err, error.RenderFailed);
1623 }
1624 
1625 fn recordRenderBundle(ptr: *anyopaque, pass: backend.RenderPass) backend.BackendError!backend.RenderBundle {
1626     const state: *State = @ptrCast(@alignCast(ptr));
1627     const raster = try state.rasterState();
1628     try state.objects.ensureUnusedCapacity(state.allocator, 1);
1629     const cb = raster_mod.beginBundle(raster) catch |err| return mapRasterError(err, error.RenderFailed);
1630     errdefer raster.discardBundle(cb);
1631     try recordPassAndEnd(state, raster, cb, pass);
1632     const draw_count: u32 = @intCast(pass.draws.len);
1633     const id = try state.putObject(.{ .render_bundle = .{ .command_buffer = cb, .draw_count = draw_count } });
1634     return .{ .id = id, .backend = .vulkan, .draw_count = draw_count };
1635 }
1636 
1637 fn submitRenderBundle(ptr: *anyopaque, request: backend.RenderBundleSubmit) backend.BackendError!void {
1638     const state: *State = @ptrCast(@alignCast(ptr));
1639     const raster = try state.rasterState();
1640     const bundle = try state.getRenderBundle(request.bundle);
1641     const stream = try state.submitStream(request.stream);
1642     var wait_storage: [raster_mod.max_wait_events]*runtime_mod.Event = undefined;
1643     const waits = try state.submitWaits(request.wait_events, &wait_storage);
1644     const signal = if (request.signal_event) |event_handle| try state.getEvent(event_handle) else null;
1645     raster_mod.submit(raster, .{
1646         .stream = stream,
1647         .cb = bundle.command_buffer,
1648         .wait_events = waits,
1649         .signal_event = signal,
1650         .retire = false,
1651     }) catch |err| return mapRasterError(err, error.RenderFailed);
1652 }
1653 
1654 fn recordPassAndEnd(
1655     state: *State,
1656     raster: *raster_mod.Raster,
1657     cb: driver_mod.VkCommandBuffer,
1658     pass: backend.RenderPass,
1659 ) backend.BackendError!void {
1660     try recordPass(state, raster, cb, pass);
1661     const drv = &raster.runtime._driver;
1662     drv.fromResult(drv.vkEndCommandBuffer(cb)) catch |err| return mapRasterError(err, error.RenderFailed);
1663 }
1664 
1665 const VertexSlot = struct {
1666     id: BackendObjectId,
1667     offset: u64,
1668     size: u64,
1669 };
1670 
1671 const IndexSlot = struct {
1672     id: BackendObjectId,
1673     offset: u64,
1674     size: u64,
1675     format: backend.RenderIndexFormat,
1676 };
1677 
1678 /// Records one pass. A draw binds only what differs from the draw before it, so a run of draws
1679 /// that share a pipeline, bindings and buffers costs one draw command each. Every range a draw
1680 /// reads is checked against its buffer, except the vertices an indexed draw reaches through its
1681 /// indices, which only the indices name.
1682 fn recordPass(
1683     state: *State,
1684     raster: *raster_mod.Raster,
1685     cb: driver_mod.VkCommandBuffer,
1686     pass: backend.RenderPass,
1687 ) backend.BackendError!void {
1688     const drv = &raster.runtime._driver;
1689     const targets = try passTargets(state, pass);
1690     raster_mod.beginPass(drv, cb, targets);
1691     var pipeline: ?*VulkanPipeline = null;
1692     var bindings_id: ?BackendObjectId = null;
1693     var vertex_slots: [raster_mod.max_vertex_buffers]?VertexSlot = @splat(null);
1694     var index_slot: ?IndexSlot = null;
1695     for (pass.draws) |draw| {
1696         const current = if (pipeline != null and pipeline.?.loaded.id == draw.pipeline.id) pipeline.? else blk: {
1697             const next = try state.getRenderPipeline(draw.pipeline);
1698             if (next.loaded.target_format != pass.color.view.format) return error.RenderArgumentMismatch;
1699             if ((next.loaded.depth != null) != (pass.depth != null)) return error.RenderArgumentMismatch;
1700             drv.vkCmdBindPipeline(cb, driver_mod.VK_PIPELINE_BIND_POINT_GRAPHICS, next.pipeline.pipeline);
1701             bindings_id = null;
1702             pipeline = next;
1703             break :blk next;
1704         };
1705         if (draw.vertex_buffers.len != current.loaded.vertex_buffer_count) return error.RenderArgumentMismatch;
1706         if ((draw.bindings != null) != (current.loaded.binding_count != 0)) return error.RenderArgumentMismatch;
1707         if (draw.bindings) |bindings| if (bindings_id != bindings.id) {
1708             const set = try state.getRenderBindings(bindings);
1709             if (set.pipeline_id != current.loaded.id) return error.RenderArgumentMismatch;
1710             drv.vkCmdBindDescriptorSets(
1711                 cb,
1712                 driver_mod.VK_PIPELINE_BIND_POINT_GRAPHICS,
1713                 current.pipeline.layout,
1714                 0,
1715                 1,
1716                 @ptrCast(&set.set.set),
1717                 0,
1718                 null,
1719             );
1720             bindings_id = bindings.id;
1721         };
1722         if (draw.push_constants.len != current.loaded.push_constant_bytes) return error.RenderArgumentMismatch;
1723         if (draw.push_constants.len != 0) {
1724             const size: u32 = @intCast(draw.push_constants.len);
1725             drv.vkCmdPushConstants(cb, current.pipeline.layout, raster_mod.push_constant_stages, 0, size, draw.push_constants.ptr);
1726         }
1727         const indexed = draw.range.index_format != .none;
1728         for (draw.vertex_buffers, current.layouts[0..draw.vertex_buffers.len]) |range, layout| {
1729             const slot = &vertex_slots[layout.binding];
1730             const bound = if (slot.*) |existing| existing.id == range.buffer.id and existing.offset == range.offset else false;
1731             if (!bound) {
1732                 const buffer = try state.getBuffer(range.buffer);
1733                 if (range.offset >= buffer.size) return error.RenderArgumentMismatch;
1734                 drv.vkCmdBindVertexBuffers(cb, layout.binding, 1, @ptrCast(&buffer._buffer), @ptrCast(&range.offset));
1735                 slot.* = .{ .id = range.buffer.id, .offset = range.offset, .size = buffer.size };
1736             }
1737             const elements: u64 = if (layout.per_instance)
1738                 @as(u64, draw.range.first_instance) + draw.range.instance_count
1739             else if (indexed)
1740                 0
1741             else
1742                 @as(u64, draw.range.first_vertex) + draw.range.vertex_count;
1743             if (elements * layout.stride > slot.*.?.size - slot.*.?.offset) return error.RenderArgumentMismatch;
1744         }
1745         if (!indexed) {
1746             if (draw.index_buffer != null) return error.RenderArgumentMismatch;
1747             drv.vkCmdDraw(cb, draw.range.vertex_count, draw.range.instance_count, draw.range.first_vertex, draw.range.first_instance);
1748             continue;
1749         }
1750         const range = draw.index_buffer orelse return error.RenderArgumentMismatch;
1751         const index_bytes: u64 = if (draw.range.index_format == .u16) 2 else 4;
1752         const bound = if (index_slot) |existing|
1753             existing.id == range.buffer.id and existing.offset == range.offset and existing.format == draw.range.index_format
1754         else
1755             false;
1756         if (!bound) {
1757             const buffer = try state.getBuffer(range.buffer);
1758             if (range.offset >= buffer.size or range.offset % index_bytes != 0) return error.RenderArgumentMismatch;
1759             const index_type = if (index_bytes == 2) driver_mod.VK_INDEX_TYPE_UINT16 else driver_mod.VK_INDEX_TYPE_UINT32;
1760             drv.vkCmdBindIndexBuffer(cb, buffer._buffer, range.offset, index_type);
1761             index_slot = .{ .id = range.buffer.id, .offset = range.offset, .size = buffer.size, .format = draw.range.index_format };
1762         }
1763         const indices = @as(u64, draw.range.first_index) + draw.range.index_count;
1764         if (indices * index_bytes > index_slot.?.size - index_slot.?.offset) return error.RenderArgumentMismatch;
1765         drv.vkCmdDrawIndexed(
1766             cb,
1767             draw.range.index_count,
1768             draw.range.instance_count,
1769             draw.range.first_index,
1770             draw.range.base_vertex,
1771             draw.range.first_instance,
1772         );
1773     }
1774     raster_mod.endPass(drv, cb, targets);
1775 }
1776 
1777 fn passTargets(state: *State, pass: backend.RenderPass) backend.BackendError!raster_mod.Targets {
1778     const color = try state.getImage(pass.color.view.texture);
1779     const depth = if (pass.depth) |attachment| try state.getImage(attachment.view.texture) else null;
1780     return .{
1781         .color = .{
1782             .image = &color.image,
1783             .clear = switch (pass.color.load) {
1784                 .load => null,
1785                 .clear => |value| .{ .color = .{ .float32 = .{ value.r, value.g, value.b, value.a } } },
1786             },
1787         },
1788         .depth = if (depth) |target| .{
1789             .image = &target.image,
1790             .clear = switch (pass.depth.?.load) {
1791                 .load => null,
1792                 .clear => |value| .{ .depthStencil = .{ .depth = value, .stencil = 0 } },
1793             },
1794         } else null,
1795         .viewport = .{
1796             .x = pass.viewport.x,
1797             .y = pass.viewport.y,
1798             .width = pass.viewport.width,
1799             .height = pass.viewport.height,
1800             .minDepth = pass.viewport.min_depth,
1801             .maxDepth = pass.viewport.max_depth,
1802         },
1803         .scissor = .{
1804             .offset = .{ .x = @intCast(pass.scissor.x), .y = @intCast(pass.scissor.y) },
1805             .extent = .{ .width = pass.scissor.width, .height = pass.scissor.height },
1806         },
1807     };
1808 }
1809 
1810 fn vulkanCompare(compare: backend.RenderCompare) driver_mod.VkCompareOp {
1811     return switch (compare) {
1812         .never => driver_mod.VK_COMPARE_OP_NEVER,
1813         .less => driver_mod.VK_COMPARE_OP_LESS,
1814         .equal => driver_mod.VK_COMPARE_OP_EQUAL,
1815         .less_equal => driver_mod.VK_COMPARE_OP_LESS_OR_EQUAL,
1816         .greater => driver_mod.VK_COMPARE_OP_GREATER,
1817         .not_equal => driver_mod.VK_COMPARE_OP_NOT_EQUAL,
1818         .greater_equal => driver_mod.VK_COMPARE_OP_GREATER_OR_EQUAL,
1819         .always => driver_mod.VK_COMPARE_OP_ALWAYS,
1820     };
1821 }
1822 
1823 fn vulkanTopology(topology: backend.RenderPrimitiveTopology) driver_mod.VkPrimitiveTopology {
1824     return switch (topology) {
1825         .triangle_list => driver_mod.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
1826         .triangle_strip => driver_mod.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
1827         .line_list => driver_mod.VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
1828         .line_strip => driver_mod.VK_PRIMITIVE_TOPOLOGY_LINE_STRIP,
1829     };
1830 }
1831 
1832 fn vertexFormat(format: backend.RenderVertexFormat) driver_mod.VkFormat {
1833     return switch (format) {
1834         .float32 => driver_mod.VK_FORMAT_R32_SFLOAT,
1835         .float32x2 => driver_mod.VK_FORMAT_R32G32_SFLOAT,
1836         .float32x3 => driver_mod.VK_FORMAT_R32G32B32_SFLOAT,
1837         .float32x4 => driver_mod.VK_FORMAT_R32G32B32A32_SFLOAT,
1838         .uint32 => driver_mod.VK_FORMAT_R32_UINT,
1839         .uint32x2 => driver_mod.VK_FORMAT_R32G32_UINT,
1840         .uint32x4 => driver_mod.VK_FORMAT_R32G32B32A32_UINT,
1841     };
1842 }
1843 
1844 /// Maps a raster runtime failure, with `fallback` for failures that belong to the request itself.
1845 fn mapRasterError(err: raster_mod.Error, fallback: backend.BackendError) backend.BackendError {
1846     return switch (err) {
1847         error.OutOfHostMemory, error.OutOfDeviceMemory, error.FragmentedPool, error.OutOfPoolMemory, error.TooManyObjects => error.OutOfMemory,
1848         error.DeviceLost => error.DeviceLost,
1849         error.StreamPoisoned => error.RenderFailed,
1850         error.FeatureNotPresent,
1851         error.FormatNotSupported,
1852         error.ExtensionNotPresent,
1853         error.LayerNotPresent,
1854         => error.CapabilityMismatch,
1855         error.DriverUnavailable,
1856         error.SymbolMissing,
1857         error.NoDevice,
1858         error.InvalidDevice,
1859         error.TimelineSemaphoreUnavailable,
1860         error.IncompatibleDriver,
1861         => error.RuntimeUnavailable,
1862         else => fallback,
1863     };
1864 }
1865 
1866 fn surfaceDescFromRequest(request: backend.SurfaceCreationRequest) backend.BackendError!runtime_mod.SurfaceDesc {
1867     const x11 = switch (request.platform) {
1868         .x11 => |x11| x11,
1869         else => return error.CapabilityMismatch,
1870     };
1871     if (x11.display == 0 or x11.window == 0) return error.InvalidSurface;
1872     const usage = imageUsageFromTextureUsage(request.usage);
1873     if (usage == 0) return error.InvalidSurface;
1874     return .{
1875         .display = x11.display,
1876         .window = x11.window,
1877         .extent = .{ .width = request.extent.width, .height = request.extent.height },
1878         .format = try vulkanFormat(request.format),
1879         .color_space = try vulkanColorSpace(request.color_space),
1880         .present_mode = vulkanPresentMode(request.present_mode),
1881         .usage = usage,
1882         .min_image_count = request.max_frames_in_flight,
1883         .composite_alpha = vulkanCompositeAlpha(request.alpha_mode),
1884     };
1885 }
1886 
1887 fn vulkanFormat(format: backend.TextureFormat) backend.BackendError!driver_mod.VkFormat {
1888     return switch (format) {
1889         .rgba8_unorm => driver_mod.VK_FORMAT_R8G8B8A8_UNORM,
1890         .bgra8_unorm => driver_mod.VK_FORMAT_B8G8R8A8_UNORM,
1891         .rgba8_srgb => driver_mod.VK_FORMAT_R8G8B8A8_SRGB,
1892         .bgra8_srgb => driver_mod.VK_FORMAT_B8G8R8A8_SRGB,
1893         .depth32_float => driver_mod.VK_FORMAT_D32_SFLOAT,
1894     };
1895 }
1896 
1897 fn vulkanColorSpace(color_space: backend.ColorSpace) backend.BackendError!driver_mod.VkColorSpaceKHR {
1898     return switch (color_space) {
1899         .srgb => driver_mod.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR,
1900         .linear => error.CapabilityMismatch,
1901     };
1902 }
1903 
1904 fn vulkanPresentMode(present_mode: backend.PresentMode) driver_mod.VkPresentModeKHR {
1905     return switch (present_mode) {
1906         .fifo => driver_mod.VK_PRESENT_MODE_FIFO_KHR,
1907         .mailbox => driver_mod.VK_PRESENT_MODE_MAILBOX_KHR,
1908         .immediate => driver_mod.VK_PRESENT_MODE_IMMEDIATE_KHR,
1909     };
1910 }
1911 
1912 fn vulkanCompositeAlpha(alpha_mode: backend.SurfaceAlphaMode) driver_mod.VkCompositeAlphaFlagBitsKHR {
1913     return switch (alpha_mode) {
1914         .solid => driver_mod.VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
1915         .premultiplied => driver_mod.VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
1916         .postmultiplied => driver_mod.VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
1917         .inherit => driver_mod.VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
1918     };
1919 }
1920 
1921 fn imageUsageFromTextureUsage(usage: backend.TextureUsage) driver_mod.VkImageUsageFlags {
1922     var flags: driver_mod.VkImageUsageFlags = 0;
1923     if (usage.copy_src) flags |= driver_mod.VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
1924     if (usage.copy_dst) flags |= driver_mod.VK_IMAGE_USAGE_TRANSFER_DST_BIT;
1925     if (usage.sampled) flags |= driver_mod.VK_IMAGE_USAGE_SAMPLED_BIT;
1926     if (usage.storage) flags |= driver_mod.VK_IMAGE_USAGE_STORAGE_BIT;
1927     if (usage.color_attachment) flags |= driver_mod.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
1928     if (usage.depth_attachment) flags |= driver_mod.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
1929     return flags;
1930 }
1931 
1932 fn textureUsageFromImageUsage(usage: driver_mod.VkImageUsageFlags) backend.TextureUsage {
1933     return .{
1934         .copy_src = (usage & driver_mod.VK_IMAGE_USAGE_TRANSFER_SRC_BIT) != 0,
1935         .copy_dst = (usage & driver_mod.VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0,
1936         .sampled = (usage & driver_mod.VK_IMAGE_USAGE_SAMPLED_BIT) != 0,
1937         .storage = (usage & driver_mod.VK_IMAGE_USAGE_STORAGE_BIT) != 0,
1938         .color_attachment = (usage & driver_mod.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0,
1939         .present = true,
1940     };
1941 }
1942 
1943 fn mapRuntimeError(err: runtime_mod.Error) backend.BackendError {
1944     return switch (err) {
1945         error.OutOfHostMemory, error.OutOfDeviceMemory, error.FragmentedPool, error.OutOfPoolMemory => error.OutOfMemory,
1946         error.DeviceLost => error.DeviceLost,
1947         error.DriverUnavailable,
1948         error.SymbolMissing,
1949         error.NoDevice,
1950         error.InvalidDevice,
1951         error.TimelineSemaphoreUnavailable,
1952         error.IncompatibleDriver,
1953         => error.RuntimeUnavailable,
1954         error.LayerNotPresent,
1955         error.ExtensionNotPresent,
1956         error.FeatureNotPresent,
1957         => error.CapabilityMismatch,
1958         error.StreamPoisoned => error.LaunchFailed,
1959         else => error.RuntimeUnavailable,
1960     };
1961 }
1962 
1963 fn mapSurfaceRuntimeError(err: runtime_mod.Error) backend.BackendError {
1964     return switch (err) {
1965         error.OutOfHostMemory, error.OutOfDeviceMemory, error.FragmentedPool, error.OutOfPoolMemory => error.OutOfMemory,
1966         error.DeviceLost => error.DeviceLost,
1967         error.SurfaceOutOfDate, error.SurfaceLost => error.SurfaceFrameExpired,
1968         error.NativeWindowInUse, error.InitializationFailed => error.InvalidSurface,
1969         error.FormatNotSupported,
1970         error.LayerNotPresent,
1971         error.ExtensionNotPresent,
1972         error.FeatureNotPresent,
1973         => error.CapabilityMismatch,
1974         error.DriverUnavailable,
1975         error.SymbolMissing,
1976         error.NoDevice,
1977         error.InvalidDevice,
1978         error.TimelineSemaphoreUnavailable,
1979         error.IncompatibleDriver,
1980         => error.RuntimeUnavailable,
1981         else => error.RuntimeUnavailable,
1982     };
1983 }
1984 
1985 fn mapArtifactError(err: launch_mod.Error) backend.BackendError {
1986     return switch (err) {
1987         error.OutOfHostMemory, error.OutOfDeviceMemory, error.FragmentedPool, error.OutOfPoolMemory => error.OutOfMemory,
1988         error.DeviceLost => error.DeviceLost,
1989         error.FormatNotSupported, error.InitializationFailed => error.InvalidArtifact,
1990         error.DriverUnavailable,
1991         error.SymbolMissing,
1992         error.NoDevice,
1993         error.InvalidDevice,
1994         error.TimelineSemaphoreUnavailable,
1995         error.IncompatibleDriver,
1996         => error.RuntimeUnavailable,
1997         error.LayerNotPresent,
1998         error.ExtensionNotPresent,
1999         error.FeatureNotPresent,
2000         => error.CapabilityMismatch,
2001         else => error.InvalidArtifact,
2002     };
2003 }
2004 
2005 fn mapAllocationError(err: runtime_mod.Error) backend.BackendError {
2006     return switch (err) {
2007         error.OutOfHostMemory, error.OutOfDeviceMemory, error.FragmentedPool, error.OutOfPoolMemory => error.OutOfMemory,
2008         error.DeviceLost => error.DeviceLost,
2009         error.MemoryMapFailed, error.InitializationFailed => error.InvalidBuffer,
2010         error.DriverUnavailable,
2011         error.SymbolMissing,
2012         error.NoDevice,
2013         error.InvalidDevice,
2014         error.TimelineSemaphoreUnavailable,
2015         error.IncompatibleDriver,
2016         => error.RuntimeUnavailable,
2017         else => error.InvalidBuffer,
2018     };
2019 }
2020 
2021 fn mapLaunchError(err: launch_mod.Error) backend.BackendError {
2022     return switch (err) {
2023         error.OutOfHostMemory, error.OutOfDeviceMemory, error.FragmentedPool, error.OutOfPoolMemory => error.OutOfMemory,
2024         error.DeviceLost => error.DeviceLost,
2025         error.StreamPoisoned => error.LaunchFailed,
2026         error.InitializationFailed => error.LaunchArgumentMismatch,
2027         error.DriverUnavailable,
2028         error.SymbolMissing,
2029         error.NoDevice,
2030         error.InvalidDevice,
2031         error.TimelineSemaphoreUnavailable,
2032         error.IncompatibleDriver,
2033         => error.RuntimeUnavailable,
2034         else => error.LaunchFailed,
2035     };
2036 }
2037 
2038 fn mapElapsedEventError(err: runtime_mod.Error) backend.BackendError {
2039     return switch (err) {
2040         error.OutOfHostMemory, error.OutOfDeviceMemory, error.FragmentedPool, error.OutOfPoolMemory => error.OutOfMemory,
2041         error.DeviceLost => error.DeviceLost,
2042         error.FeatureNotPresent => error.UnsupportedOperation,
2043         error.InitializationFailed => error.InvalidEvent,
2044         error.StreamPoisoned => error.LaunchFailed,
2045         error.DriverUnavailable,
2046         error.SymbolMissing,
2047         error.NoDevice,
2048         error.InvalidDevice,
2049         error.TimelineSemaphoreUnavailable,
2050         error.IncompatibleDriver,
2051         => error.RuntimeUnavailable,
2052         error.LayerNotPresent,
2053         error.ExtensionNotPresent,
2054         => error.CapabilityMismatch,
2055         else => error.RuntimeUnavailable,
2056     };
2057 }
2058 
2059 const vtable = backend.BackendVTable{
2060     .query_capabilities = queryCapabilities,
2061     .create_artifact = createArtifact,
2062     .load_artifact = loadArtifact,
2063     .create_render_artifact = createRenderArtifact,
2064     .load_render_artifact = loadRenderArtifact,
2065     .allocate_buffer = allocateBuffer,
2066     .allocate_texture = allocateTexture,
2067     .create_surface = createSurface,
2068     .destroy_surface = destroySurface,
2069     .destroy_texture = destroyTexture,
2070     .acquire_surface_frame = acquireSurfaceFrame,
2071     .present_surface_frame = presentSurfaceFrame,
2072     .write_surface_frame = writeSurfaceFrame,
2073     .create_stream = createStream,
2074     .create_event = createEvent,
2075     .write_buffer = writeBuffer,
2076     .read_buffer = readBuffer,
2077     .launch = launch,
2078     .render = render,
2079     .create_render_bindings = createRenderBindings,
2080     .record_render_bundle = recordRenderBundle,
2081     .submit_render_bundle = submitRenderBundle,
2082     .write_texture = writeTexture,
2083     .read_texture = readTexture,
2084     .synchronize = synchronize,
2085     .query_event = queryEvent,
2086     .record_event = recordEvent,
2087     .elapsed_event_ns = elapsedEventNs,
2088     .destroy_object = destroyObject,
2089     .deinit = deinitHandle,
2090 };
2091 
2092 test "vulkan contract reports capabilities without a live runtime" {
2093     var state = State.init(std.testing.allocator);
2094     defer state.deinit();
2095     const handle = state.handle();
2096 
2097     const caps = try handle.queryCapabilities();
2098     try std.testing.expectEqual(backend.BackendKind.vulkan, caps.identity.backend);
2099     try std.testing.expectEqual(backend.DeviceFamily.vulkan, caps.identity.family);
2100     try std.testing.expect(caps.supportsDType(.i1));
2101     try std.testing.expect(caps.supportsDType(.f32));
2102     try std.testing.expect(caps.supportsArtifactFormat(.vulkan_spirv));
2103     try std.testing.expect(caps.features.atomic_i32);
2104     try std.testing.expect(caps.features.atomic_u32);
2105     try std.testing.expect(caps.features.atomic_index);
2106     try std.testing.expect(!caps.features.atomic_f32_add_device);
2107     try std.testing.expect(!caps.features.atomic_f32_add_shared);
2108     try std.testing.expect(!caps.runtime.driver_loaded);
2109     try std.testing.expect(caps.runtime.timeline_events);
2110 }
2111 
2112 test "vulkan runtime capabilities gate narrow integer dtypes" {
2113     const caps_without_int16 = capabilitiesFrom(.{
2114         .shader_float16 = false,
2115         .shader_float64 = false,
2116         .shader_int8 = true,
2117         .shader_int16 = false,
2118         .shader_int64 = false,
2119         .storage_buffer8 = true,
2120         .storage_buffer16 = false,
2121         .subgroup_size = 0,
2122         .vendor_id = 0,
2123         .vendor_name = "stub",
2124     }, true);
2125     try std.testing.expect(caps_without_int16.supportsDType(.i8));
2126     try std.testing.expect(caps_without_int16.supportsDType(.u8));
2127     try std.testing.expect(!caps_without_int16.supportsDType(.i16));
2128     try std.testing.expect(!caps_without_int16.supportsDType(.u16));
2129 
2130     const caps_with_int16 = capabilitiesFrom(.{
2131         .shader_float16 = false,
2132         .shader_float64 = false,
2133         .shader_int8 = false,
2134         .shader_int16 = true,
2135         .shader_int64 = false,
2136         .storage_buffer8 = false,
2137         .storage_buffer16 = true,
2138         .subgroup_size = 0,
2139         .vendor_id = 0,
2140         .vendor_name = "stub",
2141     }, true);
2142     try std.testing.expect(!caps_with_int16.supportsDType(.i8));
2143     try std.testing.expect(!caps_with_int16.supportsDType(.u8));
2144     try std.testing.expect(caps_with_int16.supportsDType(.i16));
2145     try std.testing.expect(caps_with_int16.supportsDType(.u16));
2146 
2147     const caps_without_storage = capabilitiesFrom(.{
2148         .shader_float16 = true,
2149         .shader_float64 = false,
2150         .shader_int8 = true,
2151         .shader_int16 = true,
2152         .shader_int64 = false,
2153         .storage_buffer8 = false,
2154         .storage_buffer16 = false,
2155         .subgroup_size = 0,
2156         .vendor_id = 0,
2157         .vendor_name = "stub",
2158     }, true);
2159     try std.testing.expect(!caps_without_storage.supportsDType(.i1));
2160     try std.testing.expect(!caps_without_storage.supportsDType(.f16));
2161     try std.testing.expect(!caps_without_storage.supportsDType(.i8));
2162     try std.testing.expect(!caps_without_storage.supportsDType(.u8));
2163     try std.testing.expect(!caps_without_storage.supportsDType(.i16));
2164     try std.testing.expect(!caps_without_storage.supportsDType(.u16));
2165 
2166     const caps_with_64 = capabilitiesFrom(.{
2167         .shader_float16 = false,
2168         .shader_float64 = true,
2169         .shader_int8 = false,
2170         .shader_int16 = false,
2171         .shader_int64 = true,
2172         .storage_buffer8 = false,
2173         .storage_buffer16 = false,
2174         .subgroup_size = 0,
2175         .vendor_id = 0,
2176         .vendor_name = "stub",
2177     }, true);
2178     try std.testing.expect(caps_with_64.supportsDType(.f64));
2179     try std.testing.expect(caps_with_64.supportsDType(.i64));
2180     try std.testing.expect(caps_with_64.supportsDType(.u64));
2181 }
2182 
2183 test "vulkan subgroup capabilities preserve partial operation masks" {
2184     const base = Runtime.Caps{
2185         .shader_float16 = false,
2186         .shader_float64 = false,
2187         .shader_int8 = false,
2188         .shader_int16 = false,
2189         .shader_int64 = false,
2190         .storage_buffer8 = false,
2191         .storage_buffer16 = false,
2192         .subgroup_size = 32,
2193         .subgroup_supported_stages = driver_mod.VK_SHADER_STAGE_COMPUTE_BIT,
2194         .subgroup_supported_operations = driver_mod.VK_SUBGROUP_FEATURE_BASIC_BIT,
2195         .vendor_id = 0,
2196         .vendor_name = "stub",
2197     };
2198 
2199     const basic = capabilitiesFrom(base, true).subgroup;
2200     try std.testing.expect(basic.supported);
2201     try std.testing.expectEqual(@as(u32, 32), basic.size_min);
2202     try std.testing.expect(!basic.vote);
2203     try std.testing.expect(!basic.ballot);
2204     try std.testing.expect(!basic.arithmetic);
2205     try std.testing.expect(!basic.scan);
2206     try std.testing.expect(!basic.shuffle);
2207 
2208     var partial = base;
2209     partial.subgroup_supported_operations |= driver_mod.VK_SUBGROUP_FEATURE_VOTE_BIT |
2210         driver_mod.VK_SUBGROUP_FEATURE_BALLOT_BIT |
2211         driver_mod.VK_SUBGROUP_FEATURE_ARITHMETIC_BIT |
2212         driver_mod.VK_SUBGROUP_FEATURE_SHUFFLE_BIT;
2213     const partial_facts = capabilitiesFrom(partial, true).subgroup;
2214     try std.testing.expect(partial_facts.vote);
2215     try std.testing.expect(partial_facts.ballot);
2216     try std.testing.expect(partial_facts.arithmetic);
2217     try std.testing.expect(partial_facts.scan);
2218     try std.testing.expect(!partial_facts.shuffle);
2219 
2220     partial.subgroup_supported_operations |= driver_mod.VK_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT;
2221     try std.testing.expect(capabilitiesFrom(partial, true).subgroup.shuffle);
2222 
2223     var missing_basic = base;
2224     missing_basic.subgroup_supported_operations = driver_mod.VK_SUBGROUP_FEATURE_VOTE_BIT;
2225     const unsupported_operations = capabilitiesFrom(missing_basic, true).subgroup;
2226     try std.testing.expect(!unsupported_operations.supported);
2227     try std.testing.expectEqual(@as(u32, 0), unsupported_operations.size_min);
2228 
2229     var missing_compute = base;
2230     missing_compute.subgroup_supported_stages = driver_mod.VK_SHADER_STAGE_FRAGMENT_BIT;
2231     try std.testing.expect(!capabilitiesFrom(missing_compute, true).subgroup.supported);
2232 }
2233 
2234 test "vulkan contract records unsupported and unavailable phases" {
2235     var state = State.init(std.testing.allocator);
2236     defer state.deinit();
2237     const handle = state.handle();
2238 
2239     try std.testing.expectError(error.UnsupportedOperation, handle.createArtifact(.{
2240         .kernel_name = "main",
2241         .requested_format = .vulkan_spirv,
2242     }));
2243     try std.testing.expectError(error.CapabilityMismatch, handle.createArtifact(.{
2244         .kernel_name = "atomic_add",
2245         .requested_format = .vulkan_spirv,
2246         .required_features = .{ .atomic_f32_add_device = true },
2247     }));
2248     try std.testing.expectError(error.RuntimeUnavailable, handle.allocateBuffer(.{
2249         .byte_size = 16,
2250         .alignment = 16,
2251     }));
2252     try std.testing.expectError(error.RuntimeUnavailable, handle.createStream(.{}));
2253     try std.testing.expectError(error.RuntimeUnavailable, handle.createEvent(.{}));
2254     try std.testing.expectError(error.RuntimeUnavailable, handle.synchronize(.{
2255         .scope = .device,
2256     }));
2257     try std.testing.expectError(error.InvalidEvent, handle.queryEvent(.{
2258         .event = .{
2259             .id = 1,
2260             .backend = .vulkan,
2261         },
2262     }));
2263 
2264     var artifact = try backend.KernelArtifact.init(std.testing.allocator, .{
2265         .backend = .vulkan,
2266         .format = .vulkan_spirv,
2267         .entry_name = "main",
2268         .argument_count = 0,
2269     });
2270     defer artifact.deinit();
2271     const words = [_]u32{ 0x07230203, 0x00010000 };
2272     artifact.setBorrowedWords(&words);
2273 
2274     try std.testing.expectError(error.RuntimeUnavailable, handle.loadArtifact(&artifact));
2275 }
2276 
2277 test "vulkan backend reports elapsed event timing from timestamp markers" {
2278     runtime_mod.testing.resetFake();
2279     var rt = runtime_mod.testing.fakeRuntime(std.testing.allocator);
2280     defer rt._live_streams.deinit(rt.allocator);
2281 
2282     var state = State.initWithRuntime(std.testing.allocator, &rt);
2283     defer state.deinit();
2284     const handle = state.handle();
2285 
2286     const stream = try handle.createStream(.{});
2287     const start = try handle.createEvent(.{});
2288     const end = try handle.createEvent(.{});
2289 
2290     try handle.recordEvent(.{ .stream = stream, .event = start });
2291     try handle.recordEvent(.{ .stream = stream, .event = end });
2292     runtime_mod.testing.setQueryResults(&.{ 80, 120 });
2293 
2294     try std.testing.expectEqual(@as(u64, 100), try handle.elapsedEventNs(.{
2295         .start = start,
2296         .end = end,
2297     }));
2298 
2299     const snapshot = runtime_mod.testing.snapshot();
2300     try std.testing.expectEqual(@as(u32, 2), snapshot.query_pool_creates);
2301     try std.testing.expectEqual(@as(u32, 2), snapshot.cmd_write_timestamps);
2302     try std.testing.expectEqual(@as(u32, 2), snapshot.query_pool_results);
2303 }
2304 
2305 test "vulkan contract creates and presents X11 surface frames through the runtime" {
2306     runtime_mod.testing.resetFake();
2307     var rt = runtime_mod.testing.fakeRuntime(std.testing.allocator);
2308     defer rt._live_streams.deinit(rt.allocator);
2309     defer runtime_mod.testing.releaseDefaultStream(&rt);
2310 
2311     var state = State.initWithRuntime(std.testing.allocator, &rt);
2312     defer state.deinit();
2313     const handle = state.handle();
2314 
2315     const surface = try handle.createSurface(.{
2316         .platform = .{ .x11 = .{ .display = 0x5151_0000, .window = 0x6161 } },
2317         .extent = .{ .width = 640, .height = 480 },
2318         .format = .bgra8_unorm,
2319         .color_space = .srgb,
2320         .present_mode = .fifo,
2321         .usage = .{ .copy_dst = true, .color_attachment = true, .present = true },
2322         .max_frames_in_flight = 2,
2323     });
2324     try std.testing.expectEqual(backend.BackendKind.vulkan, surface.backend);
2325     try std.testing.expectEqual(backend.SurfacePlatformKind.x11, surface.platform);
2326     try std.testing.expectEqual(@as(u32, 640), surface.extent.width);
2327     try std.testing.expectEqual(@as(u32, 480), surface.extent.height);
2328 
2329     const frame = try handle.acquireSurfaceFrame(.{ .surface = surface });
2330     try std.testing.expectEqual(surface.id, frame.surface.id);
2331     try std.testing.expectEqual(backend.TextureOwnership.acquired_surface, frame.texture.ownership);
2332     try std.testing.expect(frame.texture.usage.present);
2333     try std.testing.expect(frame.texture.usage.color_attachment);
2334     try std.testing.expectError(error.SurfaceAlreadyAcquired, handle.acquireSurfaceFrame(.{ .surface = surface }));
2335     try std.testing.expectError(error.SurfaceAlreadyAcquired, handle.destroyTexture(frame.texture));
2336     try std.testing.expectError(error.SurfaceAlreadyAcquired, handle.destroySurface(surface));
2337 
2338     const wait_stream = try handle.createStream(.{});
2339     const wait_event = try handle.createEvent(.{});
2340     const signal_event = try handle.createEvent(.{});
2341     try handle.recordEvent(.{ .stream = wait_stream, .event = wait_event });
2342     const ops = [_]backend.SurfaceFrameWriteOp{
2343         .{ .clear = .{ .r = 0.02, .g = 0.04, .b = 0.08, .a = 1.0 } },
2344     };
2345     try handle.writeSurfaceFrame(.{
2346         .surface = surface,
2347         .frame = frame,
2348         .operations = &ops,
2349         .wait_events = &.{wait_event},
2350         .signal_event = signal_event,
2351     });
2352     const write_snap = runtime_mod.testing.snapshot();
2353     try std.testing.expectEqual(@as(u32, 0), write_snap.waits);
2354     try std.testing.expectEqual(@as(u32, 3), write_snap.last_submit_wait_count);
2355     try std.testing.expectEqual(@as(u32, 2), write_snap.last_submit_signal_count);
2356     runtime_mod.testing.setCounterValue(1);
2357     try std.testing.expect(try handle.queryEvent(.{ .event = signal_event }));
2358 
2359     try handle.presentSurfaceFrame(.{
2360         .surface = surface,
2361         .frame = frame,
2362     });
2363     try std.testing.expectError(error.SurfaceFrameExpired, handle.presentSurfaceFrame(.{
2364         .surface = surface,
2365         .frame = frame,
2366     }));
2367     try handle.destroyTexture(frame.texture);
2368     try handle.destroySurface(surface);
2369 
2370     const snap = runtime_mod.testing.snapshot();
2371     try std.testing.expectEqual(@as(u32, 1), snap.surface_creates);
2372     try std.testing.expectEqual(@as(u32, 1), snap.swapchain_creates);
2373     try std.testing.expectEqual(@as(u32, 1), snap.acquires);
2374     try std.testing.expectEqual(@as(u32, 1), snap.presents);
2375     try std.testing.expectEqual(@as(u32, 1), snap.swapchain_destroys);
2376     try std.testing.expectEqual(@as(u32, 1), snap.surface_destroys);
2377 }
2378 
2379 test "vulkan contract replaces surface handles for resized swapchains" {
2380     runtime_mod.testing.resetFake();
2381     var rt = runtime_mod.testing.fakeRuntime(std.testing.allocator);
2382     defer rt._live_streams.deinit(rt.allocator);
2383 
2384     var state = State.initWithRuntime(std.testing.allocator, &rt);
2385     defer state.deinit();
2386     const handle = state.handle();
2387 
2388     const old_surface = try handle.createSurface(.{
2389         .platform = .{ .x11 = .{ .display = 0x5151_0000, .window = 0x6161 } },
2390         .extent = .{ .width = 320, .height = 180 },
2391         .format = .bgra8_unorm,
2392         .color_space = .srgb,
2393         .present_mode = .fifo,
2394         .usage = .{ .copy_dst = true, .color_attachment = true, .present = true },
2395         .max_frames_in_flight = 2,
2396     });
2397     const old_frame = try handle.acquireSurfaceFrame(.{ .surface = old_surface });
2398     try handle.presentSurfaceFrame(.{
2399         .surface = old_surface,
2400         .frame = old_frame,
2401     });
2402     try handle.destroyTexture(old_frame.texture);
2403     try handle.destroySurface(old_surface);
2404 
2405     const surface = try handle.createSurface(.{
2406         .platform = .{ .x11 = .{ .display = 0x5151_0000, .window = 0x6161 } },
2407         .extent = .{ .width = 800, .height = 450 },
2408         .format = .bgra8_unorm,
2409         .color_space = .srgb,
2410         .present_mode = .fifo,
2411         .usage = .{ .copy_dst = true, .color_attachment = true, .present = true },
2412         .max_frames_in_flight = 2,
2413     });
2414     try std.testing.expect(surface.id != old_surface.id);
2415     try std.testing.expectEqual(@as(u32, 800), surface.extent.width);
2416     try std.testing.expectEqual(@as(u32, 450), surface.extent.height);
2417     try std.testing.expectError(error.InvalidSurfaceFrame, handle.presentSurfaceFrame(.{
2418         .surface = surface,
2419         .frame = old_frame,
2420     }));
2421     try std.testing.expectError(error.InvalidSurface, handle.acquireSurfaceFrame(.{ .surface = old_surface }));
2422 
2423     const frame = try handle.acquireSurfaceFrame(.{ .surface = surface });
2424     try std.testing.expectEqual(@as(u32, 800), frame.texture.extent.width);
2425     try std.testing.expectEqual(@as(u32, 450), frame.texture.extent.height);
2426     try handle.presentSurfaceFrame(.{
2427         .surface = surface,
2428         .frame = frame,
2429     });
2430     try handle.destroyTexture(frame.texture);
2431     try handle.destroySurface(surface);
2432 
2433     const snap = runtime_mod.testing.snapshot();
2434     try std.testing.expectEqual(@as(u32, 2), snap.surface_creates);
2435     try std.testing.expectEqual(@as(u32, 2), snap.swapchain_creates);
2436     try std.testing.expectEqual(@as(u32, 2), snap.acquires);
2437     try std.testing.expectEqual(@as(u32, 2), snap.presents);
2438     try std.testing.expectEqual(@as(u32, 2), snap.swapchain_destroys);
2439     try std.testing.expectEqual(@as(u32, 2), snap.surface_destroys);
2440 }
2441 
2442 const RawX11Window = struct {
2443     connection: sys.x11.Connection,
2444     window: u32,
2445     width: u16,
2446     height: u16,
2447 
2448     fn init(allocator: Allocator, width: u16, height: u16) !RawX11Window {
2449         const connection_capacity = try sys.x11.Capacity.derive(.{
2450             .reply_byte_count = sys.x11.minimum_reply_byte_count,
2451         });
2452         var connection = try sys.x11.Connection.connect(allocator, connection_capacity);
2453         errdefer connection.close();
2454         const setup = connection.setup;
2455         const window = connection.generateId();
2456         const request = connection.beginRequest();
2457         try sys.x11.protocol.createWindow(request, .{
2458             .window = window,
2459             .parent = setup.root,
2460             .depth = setup.root_depth,
2461             .visual = setup.root_visual,
2462             .width = width,
2463             .height = height,
2464             .events = sys.x11.protocol.event_mask.exposure | sys.x11.protocol.event_mask.structure_notify,
2465         });
2466         try sys.x11.protocol.windowRequest(request, .map_window, window);
2467         try connection.sendRequest();
2468         _ = waitForX11WindowEvent(&connection);
2469         return .{
2470             .connection = connection,
2471             .window = window,
2472             .width = width,
2473             .height = height,
2474         };
2475     }
2476 
2477     fn deinit(self: *RawX11Window) void {
2478         const request = self.connection.beginRequest();
2479         sys.x11.protocol.windowRequest(request, .destroy_window, self.window) catch {};
2480         self.connection.sendRequest() catch {};
2481         self.connection.close();
2482         self.* = undefined;
2483     }
2484 };
2485 
2486 fn waitForX11WindowEvent(connection: *sys.x11.Connection) bool {
2487     if (!(connection.waitReadable(1000) catch return false)) return false;
2488     _ = connection.nextMessage() catch return false;
2489     return true;
2490 }
2491 
2492 test "vulkan live xlib surface clears copies and presents a raw x11 window" {
2493     if (!build_options.vulkan_tests) return error.SkipZigTest;
2494     if (sys.env.get("DISPLAY") == null) return error.SkipZigTest;
2495 
2496     var raw = RawX11Window.init(std.testing.allocator, 64, 64) catch |err| switch (err) {
2497         error.MissingDisplay,
2498         error.RemoteDisplayUnsupported,
2499         error.InvalidDisplay,
2500         error.ConnectionFailed,
2501         error.AuthenticationFailed,
2502         error.SetupFailed,
2503         error.SetupTruncated,
2504         error.NoUsableScreen,
2505         error.NoUsableVisual,
2506         => return error.SkipZigTest,
2507         else => |actual| return actual,
2508     };
2509     defer raw.deinit();
2510 
2511     var xlib = sys.x11.xlib.openTarget(raw.connection.target) catch |err| switch (err) {
2512         error.RuntimeUnavailable,
2513         error.SymbolMissing,
2514         error.InvalidDisplay,
2515         => return error.SkipZigTest,
2516     };
2517     defer xlib.close();
2518 
2519     var state = State.initDevice(std.testing.allocator, 0) catch |err| switch (err) {
2520         error.RuntimeUnavailable => return error.SkipZigTest,
2521         else => |actual| return actual,
2522     };
2523     defer state.deinit();
2524     const handle = state.handle();
2525 
2526     const surface = handle.createSurface(.{
2527         .platform = .{ .x11 = .{
2528             .display = @intFromPtr(xlib.display),
2529             .window = raw.window,
2530             .visual_id = raw.connection.setup.root_visual,
2531             .depth = raw.connection.setup.root_depth,
2532         } },
2533         .extent = .{ .width = raw.width, .height = raw.height },
2534         .format = .bgra8_unorm,
2535         .color_space = .srgb,
2536         .present_mode = .fifo,
2537         .usage = .{ .copy_dst = true, .color_attachment = true, .present = true },
2538         .max_frames_in_flight = 2,
2539     }) catch |err| switch (err) {
2540         error.RuntimeUnavailable,
2541         error.CapabilityMismatch,
2542         => return error.SkipZigTest,
2543         else => |actual| return actual,
2544     };
2545     errdefer handle.destroySurface(surface) catch {};
2546 
2547     const frame = try handle.acquireSurfaceFrame(.{ .surface = surface });
2548     try std.testing.expectEqual(surface.id, frame.surface.id);
2549     try std.testing.expectEqual(backend.TextureOwnership.acquired_surface, frame.texture.ownership);
2550 
2551     const pixel_count = try std.math.mul(
2552         usize,
2553         @as(usize, frame.texture.extent.width),
2554         @as(usize, frame.texture.extent.height),
2555     );
2556     const byte_count = try std.math.mul(usize, pixel_count, 4);
2557     const pixels = try std.testing.allocator.alloc(u8, byte_count);
2558     defer std.testing.allocator.free(pixels);
2559     var pixel: usize = 0;
2560     while (pixel < pixel_count) : (pixel += 1) {
2561         const offset = pixel * 4;
2562         pixels[offset + 0] = 0x10;
2563         pixels[offset + 1] = 0x80;
2564         pixels[offset + 2] = 0xd0;
2565         pixels[offset + 3] = 0xff;
2566     }
2567 
2568     const staging = try handle.allocateBuffer(.{
2569         .byte_size = pixels.len,
2570         .alignment = 16,
2571     });
2572     defer handle.destroyObject(staging.id);
2573     var submitted_surface_work = false;
2574     defer if (submitted_surface_work) handle.synchronize(.{ .scope = .device }) catch {};
2575 
2576     try handle.writeBuffer(.{
2577         .handle = staging,
2578         .bytes = pixels,
2579     });
2580 
2581     const ops = [_]backend.SurfaceFrameWriteOp{
2582         .{ .clear = .{ .r = 0.02, .g = 0.04, .b = 0.08, .a = 1.0 } },
2583         .{ .copy_buffer = staging },
2584     };
2585     try handle.writeSurfaceFrame(.{
2586         .surface = surface,
2587         .frame = frame,
2588         .operations = &ops,
2589     });
2590     submitted_surface_work = true;
2591 
2592     try handle.presentSurfaceFrame(.{
2593         .surface = surface,
2594         .frame = frame,
2595     });
2596     try handle.synchronize(.{ .scope = .device });
2597     submitted_surface_work = false;
2598     try handle.destroyTexture(frame.texture);
2599     try handle.destroySurface(surface);
2600 }
2601 
2602 test "vulkan contract rejects direct artifact creation before runtime access" {
2603     var state = State.init(std.testing.allocator);
2604     defer state.deinit();
2605     const handle = state.handle();
2606 
2607     try std.testing.expectError(error.UnsupportedOperation, handle.createArtifact(.{
2608         .kernel_name = "direct_add_f32",
2609         .requested_format = .vulkan_spirv,
2610         .required_dtypes = backend.DTypeSet.init(&.{.f32}),
2611         .diagnostic_id = "choir/kernel/0",
2612     }));
2613 }
2614 
2615 test "vulkan contract creates SPIR-V artifact before runtime access" {
2616     var state = State.init(std.testing.allocator);
2617     defer state.deinit();
2618     const handle = state.handle();
2619 
2620     const words = [_]u32{ 0x07230203, 0x00010000, 0, 0, 0, 0 };
2621     var artifact = try handle.createArtifact(.{
2622         .kernel_name = "vulkan_choir_copy_i32",
2623         .requested_format = .vulkan_spirv,
2624         .argument_count = 2,
2625         .required_dtypes = backend.DTypeSet.init(&.{.i32}),
2626         .diagnostic_id = "vulkan_choir_copy_i32",
2627         .payload = .{ .words_u32 = &words },
2628     });
2629     defer artifact.deinit();
2630 
2631     try std.testing.expectEqual(backend.BackendKind.vulkan, artifact.backend);
2632     try std.testing.expectEqual(backend.ArtifactFormat.vulkan_spirv, artifact.format);
2633     try std.testing.expectEqualStrings("vulkan_choir_copy_i32", artifact.entry_name);
2634     try std.testing.expectEqual(@as(u32, 2), artifact.argument_count);
2635     try std.testing.expectEqualStrings("vulkan_choir_copy_i32", artifact.diagnostic_id.?);
2636     try std.testing.expect(artifact.payload.words_u32.len > 5);
2637     try std.testing.expectEqual(@as(u32, 0x07230203), artifact.payload.words_u32[0]);
2638     try std.testing.expectError(error.RuntimeUnavailable, handle.loadArtifact(&artifact));
2639 }
2640 
2641 test "vulkan contract validates artifact format before runtime access" {
2642     var state = State.init(std.testing.allocator);
2643     defer state.deinit();
2644     const handle = state.handle();
2645 
2646     var artifact = try backend.KernelArtifact.init(std.testing.allocator, .{
2647         .backend = .vulkan,
2648         .format = .cuda_ptx,
2649         .entry_name = "main",
2650         .argument_count = 0,
2651     });
2652     defer artifact.deinit();
2653     artifact.setBorrowedText("// ptx");
2654 
2655     try std.testing.expectError(error.UnsupportedArtifactFormat, handle.loadArtifact(&artifact));
2656 }