authorgravatar for gotaseven@gmail.comGota7 <gotaseven@gmail.com> 2026-07-18 10:09:46+02:00
committergravatar for alichraghi@noreply.codeberg.orgAli Cheraghi <alichraghi@noreply.codeberg.org> 2026-07-18 10:09:46+02:00
logecb9b3d16dd5c5a18d376c2e312e0a0a65fd3014
tree2dd5169028d2423fb232d8dd4afcf8eb663c60f5
parent134a2c666aed48c40f8dd4525e53fe890af7e07a

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 <alichraghi@noreply.codeberg.org>

1 files changed, 151 insertions(+), 2 deletions(-)

lib/std/spirv.zig+151-2
......@@ -1,3 +1,4 @@
1const builtin = @import("builtin");
12const std = @import("std.zig");
23
34pub 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 {
9697 },
9798 .int, .float => return asm (
9899 \\%ret = OpSpecConstant %ty $default_value
99 \\OpDecorate %ret SpecId $spec_id"
100 \\ OpDecorate %ret SpecId $spec_id"
100101 : [ret] "" (-> T),
101102 : [ty] "t" (T),
102103 [default_value] "c" (default_value),
......@@ -109,6 +110,154 @@ pub fn specConst(T: type, comptime default_value: T, comptime spec_id: u32) T {
109110 [default_value] "c" (default_value),
110111 [spec_id] "c" (spec_id),
111112 ),
112 else => @compileError("unsupported spec constant type"),
113 else => @compileError("Invalid spec-constant type"),
113114 }
114115}
116
117/// Get the type that specifies a coordinate for a SPIR-V image or sampled image.
118fn ImageCoordinate(Image: type, Element: type) type {
119 const image_info = switch (@typeInfo(Image)) {
120 .spirv => |spirv| switch (spirv) {
121 .sampled_image => |sampled_image| @typeInfo(sampled_image).spirv.image,
122 .image => |image| image,
123 else => @compileError("Expected SPIR-V image or sampled image type, found '" ++ @typeName(Image) ++ "'"),
124 },
125 else => @compileError("Expected SPIR-V image or sampled image type, found '" ++ @typeName(Image) ++ "'"),
126 };
127 const dim = switch (image_info.dim) {
128 .@"1d" => 1 + @as(u8, @intFromBool(image_info.arrayed)),
129 .@"2d" => 2 + @as(u8, @intFromBool(image_info.arrayed)),
130 .@"3d", .cube => 3 + @as(u8, @intFromBool(image_info.arrayed)),
131 };
132 if (dim == 1) return Element else return @Vector(dim, Element);
133}
134
135/// The type of the components that result from sampling or reading from the given SPIR-V image or sampled image type.
136fn ImageSampledType(Image: type) type {
137 const image_info = switch (@typeInfo(Image)) {
138 .spirv => |spirv| switch (spirv) {
139 .sampled_image => |sampled_image| @typeInfo(sampled_image).spirv.image,
140 .image => |image| image,
141 else => @compileError("Expected SPIR-V image or sampled image type, found '" ++ @typeName(Image) ++ "'"),
142 },
143 else => @compileError("Expected SPIR-V image or sampled image type, found '" ++ @typeName(Image) ++ "'"),
144 };
145 return switch (image_info.usage) {
146 inline else => |usage| usage,
147 };
148}
149
150/// The type of `sampled_image` must be a pointer to a SPIR-V sampled image.
151pub fn imageSampleImplicitLod(
152 sampled_image: anytype,
153 coordinate: ImageCoordinate(std.meta.Child(@TypeOf(sampled_image)), f32),
154) @Vector(4, ImageSampledType(std.meta.Child(@TypeOf(sampled_image)))) {
155 const SampledImage = switch (@typeInfo(@TypeOf(sampled_image))) {
156 .pointer => |pointer| pointer.child,
157 else => @compileError("Expected a pointer to SPIR-V sampled image type, found '" ++ @typeName(@TypeOf(sampled_image)) ++ "'"),
158 };
159 const Result = @Vector(4, ImageSampledType(SampledImage));
160
161 const image_info = switch (@typeInfo(SampledImage)) {
162 .spirv => |spirv| switch (spirv) {
163 .sampled_image => |sampled_image_info| @typeInfo(sampled_image_info).spirv.image,
164 else => @compileError("Expected SPIR-V sampled image type, found '" ++ @typeName(SampledImage) ++ "'"),
165 },
166 else => @compileError("Expected SPIR-V sampled image type, found '" ++ @typeName(SampledImage) ++ "'"),
167 };
168
169 if (image_info.multisampled)
170 @compileError("Can not implicitly sample a sampled image that was multisampled");
171
172 // TOOD: If buffer dim is added, throw a compile error if the dimension is a buffer.
173
174 return asm volatile (
175 \\%loaded_sampler = OpLoad %SampledImage %sampled_image
176 \\%ret = OpImageSampleImplicitLod %Result %loaded_sampler %coordinate
177 : [ret] "" (-> Result),
178 : [SampledImage] "t" (SampledImage),
179 [sampled_image] "" (sampled_image),
180 [Result] "t" (Result),
181 [coordinate] "" (coordinate),
182 );
183}
184
185/// Query the dimensions of `image`, with no level of detail.
186pub fn imageQuerySize(
187 image: anytype,
188) ImageCoordinate(std.meta.Child(@TypeOf(image)), u32) {
189 const Image = switch (@typeInfo(@TypeOf(image))) {
190 .pointer => |pointer| pointer.child,
191 else => @compileError("Expected a pointer to SPIR-V image type, found '" ++ @typeName(@TypeOf(image)) ++ "'"),
192 };
193
194 const image_info = switch (@typeInfo(Image)) {
195 .spirv => |spirv| switch (spirv) {
196 .image => |info| info,
197 else => @compileError("Expected SPIR-V image type, found '" ++ @typeName(Image) ++ "'"),
198 },
199 else => @compileError("Expected SPIR-V image type, found '" ++ @typeName(Image) ++ "'"),
200 };
201
202 // TODO: Remove this check if dimension is not 1d, 2d, 3d, or cube (in case buffer is added).
203 if (!image_info.multisampled and image_info.usage != .unknown and image_info.usage != .storage)
204 @compileError("SPIR-V image must be either be multisampled or have an unknown or storage usage");
205
206 const Result = ImageCoordinate(std.meta.Child(@TypeOf(image)), u32);
207
208 return asm volatile (
209 \\%loaded_image = OpLoad %Image %image
210 \\%ret = OpImageQuerySize %Result %loaded_image
211 : [ret] "" (-> Result),
212 : [Image] "t" (Image),
213 [image] "" (image),
214 [Result] "t" (Result),
215 );
216}
217
218/// Write a texel to an image without a sampler.
219/// The type of `image` must be a pointer to a SPIR-V image.
220pub fn imageWrite(
221 image: anytype,
222 T: type,
223 coordinate: ImageCoordinate(std.meta.Child(@TypeOf(image)), T),
224 texel: @Vector(4, ImageSampledType(std.meta.Child(@TypeOf(image)))),
225) void {
226 switch (T) {
227 u32, i32 => {},
228 f32 => if (builtin.target.os.tag != .opencl) {
229 @compileError("Floating point image coordinates only supported by OpenCL");
230 },
231 else => @compileError("Expected one of u32, i32 and f32 types. Found '" ++ @typeName(T) ++ "'"),
232 }
233
234 const Image = switch (@typeInfo(@TypeOf(image))) {
235 .pointer => |pointer| pointer.child,
236 else => @compileError("Expected a pointer to SPIR-V image type, found '" ++ @typeName(@TypeOf(image)) ++ "'"),
237 };
238
239 const image_info = switch (@typeInfo(Image)) {
240 .spirv => |spirv| switch (spirv) {
241 .image => |info| info,
242 else => @compileError("Expected SPIR-V image type, found '" ++ @typeName(Image) ++ "'"),
243 },
244 else => @compileError("Expected SPIR-V image type, found '" ++ @typeName(Image) ++ "'"),
245 };
246
247 switch (image_info.usage) {
248 .unknown, .storage => {},
249 else => @compileError("SPIR-V image must have unknown or storage usage"),
250 }
251
252 // TODO: If SubpassData dim is added, throw a compiler error if the image is arrayed and has the SubpassData dim.
253
254 return asm volatile (
255 \\%loaded_image = OpLoad %Image %image
256 \\ OpImageWrite %loaded_image %coordinate %texel
257 :
258 : [Image] "t" (Image),
259 [image] "" (image),
260 [coordinate] "" (coordinate),
261 [texel] "" (texel),
262 );
263}