pub fn controlBarrier(comptime execution: Scope, comptime memory: Scope, comptime semantics: MemorySemantics) voidpub fn memoryBarrier(comptime memory: Scope, comptime semantics: MemorySemantics) voidpub fn workgroupBarrier() voidpub fn specConst(T: type, comptime default_value: T, comptime spec_id: u32) Tpub fn imageSampleImplicitLod(sampled_image: anytype, coordinate: ImageCoordinate(std.meta.Child(@TypeOf(sampled_image)), f32)) @Vector(4, ImageSampledType(std.meta.Child(@TypeOf(sampled_image))))The type of sampled_image must be a pointer to a SPIR-V sampled image.
pub fn imageQuerySize(image: anytype) ImageCoordinate(std.meta.Child(@TypeOf(image)), u32)Query the dimensions of image, with no level of detail.
pub fn imageWrite(image: anytype, T: type, coordinate: ImageCoordinate(std.meta.Child(@TypeOf(image)), T), texel: @Vector(4, ImageSampledType(std.meta.Child(@TypeOf(image))))) voidWrite a texel to an image without a sampler. The type of image must be a pointer to a SPIR-V image.
pub fn setMeshOutputs(vertex_count: u32, primitive_count: u32) void1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273const builtin = @import("builtin");
const std = @import("std.zig");
pub const position_in = @extern(*addrspace(.input) @Vector(4, f32), .{ .name = "position" });
pub const position_out = @extern(*addrspace(.output) @Vector(4, f32), .{ .name = "position" });
pub const point_size_in = @extern(*addrspace(.input) f32, .{ .name = "point_size" });
pub const point_size_out = @extern(*addrspace(.output) f32, .{ .name = "point_size" });
pub extern const invocation_id: u32 addrspace(.input);
pub extern const frag_coord: @Vector(4, f32) addrspace(.input);
pub extern const point_coord: @Vector(2, f32) addrspace(.input);
// TODO: direct/indirect values
// pub extern const front_facing: bool addrspace(.input);
// TODO: runtime array
// pub extern const sample_mask;
pub extern var frag_depth: f32 addrspace(.output);
pub extern const num_workgroups: @Vector(3, u32) addrspace(.input);
pub extern const workgroup_size: @Vector(3, u32) addrspace(.input);
pub extern const workgroup_id: @Vector(3, u32) addrspace(.input);
pub extern const local_invocation_id: @Vector(3, u32) addrspace(.input);
pub extern const global_invocation_id: @Vector(3, u32) addrspace(.input);
pub extern const vertex_index: u32 addrspace(.input);
pub extern const instance_index: u32 addrspace(.input);
pub const Scope = enum(u32) {
cross_device = 0,
device = 1,
workgroup = 2,
subgroup = 3,
invocation = 4,
queue_family = 5,
shader_call_khr = 6,
};
pub const MemorySemantics = packed struct(u32) {
_reserved_bit_0: bool = false,
acquire: bool = false,
release: bool = false,
acquire_release: bool = false,
sequentially_consistent: bool = false,
_reserved_bit_5: bool = false,
uniform_memory: bool = false,
subgroup_memory: bool = false,
workgroup_memory: bool = false,
cross_workgroup_memory: bool = false,
atomic_counter_memory: bool = false,
image_memory: bool = false,
output_memory: bool = false,
make_available: bool = false,
make_visible: bool = false,
@"volatile": bool = false,
_reserved: u16 = 0,
pub const none: MemorySemantics = .{};
};
pub fn controlBarrier(
comptime execution: Scope,
comptime memory: Scope,
comptime semantics: MemorySemantics,
) void {
asm volatile (
\\OpControlBarrier %exec %mem %sem
:
: [exec] "" (@as(u32, @backingInt(execution))),
[mem] "" (@as(u32, @backingInt(memory))),
[sem] "" (@as(u32, @bitCast(semantics))),
);
}
pub fn memoryBarrier(comptime memory: Scope, comptime semantics: MemorySemantics) void {
asm volatile (
\\OpMemoryBarrier %mem %sem
:
: [mem] "" (@as(u32, @backingInt(memory))),
[sem] "" (@as(u32, @bitCast(semantics))),
);
}
pub fn workgroupBarrier() void {
controlBarrier(
.workgroup,
.workgroup,
.{ .acquire_release = true, .workgroup_memory = true },
);
}
pub fn specConst(T: type, comptime default_value: T, comptime spec_id: u32) T {
switch (@typeInfo(T)) {
.bool => {
const op = if (default_value) "OpSpecConstantTrue" else "OpSpecConstantFalse";
return asm ("%ret = " ++ op ++ " %ty\n" ++
"OpDecorate %ret SpecId $spec_id"
: [ret] "" (-> T),
: [ty] "t" (T),
[spec_id] "c" (spec_id),
);
},
.int, .float => return asm (
\\%ret = OpSpecConstant %ty $default_value
\\ OpDecorate %ret SpecId $spec_id
: [ret] "" (-> T),
: [ty] "t" (T),
[default_value] "c" (default_value),
[spec_id] "c" (spec_id),
),
.vector => return asm (
\\%ret = OpSpecConstantComposite %ty %default_value %spec_id
: [ret] "" (-> T),
: [ty] "t" (T),
[default_value] "c" (default_value),
[spec_id] "c" (spec_id),
),
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),
);
}
pub fn setMeshOutputs(vertex_count: u32, primitive_count: u32) void {
asm volatile (
\\OpSetMeshOutputsEXT %vertex_count %primitive_count
:
: [vertex_count] "" (vertex_count),
[primitive_count] "" (primitive_count),
);
}