From ecb9b3d16dd5c5a18d376c2e312e0a0a65fd3014 Mon Sep 17 00:00:00 2001 From: Gota7 Date: Sat, 18 Jul 2026 10:09:46 +0200 Subject: [PATCH] Add helpful SPIR-V functions (#36187) The following capabilities are added to `std.spirv`: * Implicit image sampling * Image writing Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36187 Reviewed-by: Ali Cheraghi --- lib/std/spirv.zig | 153 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/lib/std/spirv.zig b/lib/std/spirv.zig index b88c07df9b33b40ab707374336e3d18d390b8e66..72ca757610e55922d552ccd8fb812b349d3f5c5f 100644 --- a/lib/std/spirv.zig +++ b/lib/std/spirv.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std.zig"); pub const position_in = @extern(*addrspace(.input) @Vector(4, f32), .{ .name = "position" }); @@ -96,7 +97,7 @@ pub fn specConst(T: type, comptime default_value: T, comptime spec_id: u32) T { }, .int, .float => return asm ( \\%ret = OpSpecConstant %ty $default_value - \\OpDecorate %ret SpecId $spec_id" + \\ OpDecorate %ret SpecId $spec_id" : [ret] "" (-> T), : [ty] "t" (T), [default_value] "c" (default_value), @@ -109,6 +110,154 @@ pub fn specConst(T: type, comptime default_value: T, comptime spec_id: u32) T { [default_value] "c" (default_value), [spec_id] "c" (spec_id), ), - else => @compileError("unsupported spec constant type"), + else => @compileError("Invalid spec-constant type"), } } + +/// Get the type that specifies a coordinate for a SPIR-V image or sampled image. +fn ImageCoordinate(Image: type, Element: type) type { + const image_info = switch (@typeInfo(Image)) { + .spirv => |spirv| switch (spirv) { + .sampled_image => |sampled_image| @typeInfo(sampled_image).spirv.image, + .image => |image| image, + else => @compileError("Expected SPIR-V image or sampled image type, found '" ++ @typeName(Image) ++ "'"), + }, + else => @compileError("Expected SPIR-V image or sampled image type, found '" ++ @typeName(Image) ++ "'"), + }; + const dim = switch (image_info.dim) { + .@"1d" => 1 + @as(u8, @intFromBool(image_info.arrayed)), + .@"2d" => 2 + @as(u8, @intFromBool(image_info.arrayed)), + .@"3d", .cube => 3 + @as(u8, @intFromBool(image_info.arrayed)), + }; + if (dim == 1) return Element else return @Vector(dim, Element); +} + +/// The type of the components that result from sampling or reading from the given SPIR-V image or sampled image type. +fn ImageSampledType(Image: type) type { + const image_info = switch (@typeInfo(Image)) { + .spirv => |spirv| switch (spirv) { + .sampled_image => |sampled_image| @typeInfo(sampled_image).spirv.image, + .image => |image| image, + else => @compileError("Expected SPIR-V image or sampled image type, found '" ++ @typeName(Image) ++ "'"), + }, + else => @compileError("Expected SPIR-V image or sampled image type, found '" ++ @typeName(Image) ++ "'"), + }; + return switch (image_info.usage) { + inline else => |usage| usage, + }; +} + +/// The type of `sampled_image` must be a pointer to a SPIR-V sampled image. +pub fn imageSampleImplicitLod( + sampled_image: anytype, + coordinate: ImageCoordinate(std.meta.Child(@TypeOf(sampled_image)), f32), +) @Vector(4, ImageSampledType(std.meta.Child(@TypeOf(sampled_image)))) { + const SampledImage = switch (@typeInfo(@TypeOf(sampled_image))) { + .pointer => |pointer| pointer.child, + else => @compileError("Expected a pointer to SPIR-V sampled image type, found '" ++ @typeName(@TypeOf(sampled_image)) ++ "'"), + }; + const Result = @Vector(4, ImageSampledType(SampledImage)); + + const image_info = switch (@typeInfo(SampledImage)) { + .spirv => |spirv| switch (spirv) { + .sampled_image => |sampled_image_info| @typeInfo(sampled_image_info).spirv.image, + else => @compileError("Expected SPIR-V sampled image type, found '" ++ @typeName(SampledImage) ++ "'"), + }, + else => @compileError("Expected SPIR-V sampled image type, found '" ++ @typeName(SampledImage) ++ "'"), + }; + + if (image_info.multisampled) + @compileError("Can not implicitly sample a sampled image that was multisampled"); + + // TOOD: If buffer dim is added, throw a compile error if the dimension is a buffer. + + return asm volatile ( + \\%loaded_sampler = OpLoad %SampledImage %sampled_image + \\%ret = OpImageSampleImplicitLod %Result %loaded_sampler %coordinate + : [ret] "" (-> Result), + : [SampledImage] "t" (SampledImage), + [sampled_image] "" (sampled_image), + [Result] "t" (Result), + [coordinate] "" (coordinate), + ); +} + +/// Query the dimensions of `image`, with no level of detail. +pub fn imageQuerySize( + image: anytype, +) ImageCoordinate(std.meta.Child(@TypeOf(image)), u32) { + const Image = switch (@typeInfo(@TypeOf(image))) { + .pointer => |pointer| pointer.child, + else => @compileError("Expected a pointer to SPIR-V image type, found '" ++ @typeName(@TypeOf(image)) ++ "'"), + }; + + const image_info = switch (@typeInfo(Image)) { + .spirv => |spirv| switch (spirv) { + .image => |info| info, + else => @compileError("Expected SPIR-V image type, found '" ++ @typeName(Image) ++ "'"), + }, + else => @compileError("Expected SPIR-V image type, found '" ++ @typeName(Image) ++ "'"), + }; + + // TODO: Remove this check if dimension is not 1d, 2d, 3d, or cube (in case buffer is added). + if (!image_info.multisampled and image_info.usage != .unknown and image_info.usage != .storage) + @compileError("SPIR-V image must be either be multisampled or have an unknown or storage usage"); + + const Result = ImageCoordinate(std.meta.Child(@TypeOf(image)), u32); + + return asm volatile ( + \\%loaded_image = OpLoad %Image %image + \\%ret = OpImageQuerySize %Result %loaded_image + : [ret] "" (-> Result), + : [Image] "t" (Image), + [image] "" (image), + [Result] "t" (Result), + ); +} + +/// Write a texel to an image without a sampler. +/// The type of `image` must be a pointer to a SPIR-V image. +pub fn imageWrite( + image: anytype, + T: type, + coordinate: ImageCoordinate(std.meta.Child(@TypeOf(image)), T), + texel: @Vector(4, ImageSampledType(std.meta.Child(@TypeOf(image)))), +) void { + switch (T) { + u32, i32 => {}, + f32 => if (builtin.target.os.tag != .opencl) { + @compileError("Floating point image coordinates only supported by OpenCL"); + }, + else => @compileError("Expected one of u32, i32 and f32 types. Found '" ++ @typeName(T) ++ "'"), + } + + const Image = switch (@typeInfo(@TypeOf(image))) { + .pointer => |pointer| pointer.child, + else => @compileError("Expected a pointer to SPIR-V image type, found '" ++ @typeName(@TypeOf(image)) ++ "'"), + }; + + const image_info = switch (@typeInfo(Image)) { + .spirv => |spirv| switch (spirv) { + .image => |info| info, + else => @compileError("Expected SPIR-V image type, found '" ++ @typeName(Image) ++ "'"), + }, + else => @compileError("Expected SPIR-V image type, found '" ++ @typeName(Image) ++ "'"), + }; + + switch (image_info.usage) { + .unknown, .storage => {}, + else => @compileError("SPIR-V image must have unknown or storage usage"), + } + + // TODO: If SubpassData dim is added, throw a compiler error if the image is arrayed and has the SubpassData dim. + + return asm volatile ( + \\%loaded_image = OpLoad %Image %image + \\ OpImageWrite %loaded_image %coordinate %texel + : + : [Image] "t" (Image), + [image] "" (image), + [coordinate] "" (coordinate), + [texel] "" (texel), + ); +} -- 2.54.0