1 // Copyright 2018 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "common/Constants.h"
16
17 #include "dawn_native/ShaderModule.h"
18
19 #include "tests/unittests/validation/ValidationTest.h"
20
21 #include "utils/WGPUHelpers.h"
22
23 #include <sstream>
24
25 class ShaderModuleValidationTest : public ValidationTest {};
26
27 // Test case with a simpler shader that should successfully be created
TEST_F(ShaderModuleValidationTest,CreationSuccess)28 TEST_F(ShaderModuleValidationTest, CreationSuccess) {
29 const char* shader = R"(
30 OpCapability Shader
31 %1 = OpExtInstImport "GLSL.std.450"
32 OpMemoryModel Logical GLSL450
33 OpEntryPoint Fragment %main "main" %fragColor
34 OpExecutionMode %main OriginUpperLeft
35 OpSource GLSL 450
36 OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
37 OpSourceExtension "GL_GOOGLE_include_directive"
38 OpName %main "main"
39 OpName %fragColor "fragColor"
40 OpDecorate %fragColor Location 0
41 %void = OpTypeVoid
42 %3 = OpTypeFunction %void
43 %float = OpTypeFloat 32
44 %v4float = OpTypeVector %float 4
45 %_ptr_Output_v4float = OpTypePointer Output %v4float
46 %fragColor = OpVariable %_ptr_Output_v4float Output
47 %float_1 = OpConstant %float 1
48 %float_0 = OpConstant %float 0
49 %12 = OpConstantComposite %v4float %float_1 %float_0 %float_0 %float_1
50 %main = OpFunction %void None %3
51 %5 = OpLabel
52 OpStore %fragColor %12
53 OpReturn
54 OpFunctionEnd)";
55
56 utils::CreateShaderModuleFromASM(device, shader);
57 }
58
59 // Tests that if the output location exceeds kMaxColorAttachments the fragment shader will fail to
60 // be compiled.
TEST_F(ShaderModuleValidationTest,FragmentOutputLocationExceedsMaxColorAttachments)61 TEST_F(ShaderModuleValidationTest, FragmentOutputLocationExceedsMaxColorAttachments) {
62 std::ostringstream stream;
63 stream << "[[stage(fragment)]] fn main() -> [[location(" << kMaxColorAttachments
64 << R"()]] vec4<f32> {
65 return vec4<f32>(0.0, 1.0, 0.0, 1.0);
66 })";
67 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, stream.str().c_str()));
68 }
69
70 // Test that it is invalid to create a shader module with no chained descriptor. (It must be
71 // WGSL or SPIRV, not empty)
TEST_F(ShaderModuleValidationTest,NoChainedDescriptor)72 TEST_F(ShaderModuleValidationTest, NoChainedDescriptor) {
73 wgpu::ShaderModuleDescriptor desc = {};
74 ASSERT_DEVICE_ERROR(device.CreateShaderModule(&desc));
75 }
76
77 // Test that it is not allowed to use combined texture and sampler.
TEST_F(ShaderModuleValidationTest,CombinedTextureAndSampler)78 TEST_F(ShaderModuleValidationTest, CombinedTextureAndSampler) {
79 // SPIR-V ASM produced by glslang for the following fragment shader:
80 //
81 // #version 450
82 // layout(set = 0, binding = 0) uniform sampler2D tex;
83 // void main () {}
84 //
85 // Note that the following defines an interface combined texture/sampler which is not allowed
86 // in Dawn / WebGPU.
87 //
88 // %8 = OpTypeSampledImage %7
89 // %_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
90 // %tex = OpVariable %_ptr_UniformConstant_8 UniformConstant
91 const char* shader = R"(
92 OpCapability Shader
93 %1 = OpExtInstImport "GLSL.std.450"
94 OpMemoryModel Logical GLSL450
95 OpEntryPoint Fragment %main "main"
96 OpExecutionMode %main OriginUpperLeft
97 OpSource GLSL 450
98 OpName %main "main"
99 OpName %tex "tex"
100 OpDecorate %tex DescriptorSet 0
101 OpDecorate %tex Binding 0
102 %void = OpTypeVoid
103 %3 = OpTypeFunction %void
104 %float = OpTypeFloat 32
105 %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
106 %8 = OpTypeSampledImage %7
107 %_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
108 %tex = OpVariable %_ptr_UniformConstant_8 UniformConstant
109 %main = OpFunction %void None %3
110 %5 = OpLabel
111 OpReturn
112 OpFunctionEnd
113 )";
114
115 ASSERT_DEVICE_ERROR(utils::CreateShaderModuleFromASM(device, shader));
116 }
117
118 // Test that it is not allowed to declare a multisampled-array interface texture.
119 // TODO(enga): Also test multisampled cube, cube array, and 3D. These have no GLSL keywords.
TEST_F(ShaderModuleValidationTest,MultisampledArrayTexture)120 TEST_F(ShaderModuleValidationTest, MultisampledArrayTexture) {
121 // SPIR-V ASM produced by glslang for the following fragment shader:
122 //
123 // #version 450
124 // layout(set=0, binding=0) uniform texture2DMSArray tex;
125 // void main () {}}
126 //
127 // Note that the following defines an interface array multisampled texture which is not allowed
128 // in Dawn / WebGPU.
129 //
130 // %7 = OpTypeImage %float 2D 0 1 1 1 Unknown
131 // %_ptr_UniformConstant_7 = OpTypePointer UniformConstant %7
132 // %tex = OpVariable %_ptr_UniformConstant_7 UniformConstant
133 const char* shader = R"(
134 OpCapability Shader
135 %1 = OpExtInstImport "GLSL.std.450"
136 OpMemoryModel Logical GLSL450
137 OpEntryPoint Fragment %main "main"
138 OpExecutionMode %main OriginUpperLeft
139 OpSource GLSL 450
140 OpName %main "main"
141 OpName %tex "tex"
142 OpDecorate %tex DescriptorSet 0
143 OpDecorate %tex Binding 0
144 %void = OpTypeVoid
145 %3 = OpTypeFunction %void
146 %float = OpTypeFloat 32
147 %7 = OpTypeImage %float 2D 0 1 1 1 Unknown
148 %_ptr_UniformConstant_7 = OpTypePointer UniformConstant %7
149 %tex = OpVariable %_ptr_UniformConstant_7 UniformConstant
150 %main = OpFunction %void None %3
151 %5 = OpLabel
152 OpReturn
153 OpFunctionEnd
154 )";
155
156 ASSERT_DEVICE_ERROR(utils::CreateShaderModuleFromASM(device, shader));
157 }
158
159 // Tests that shader module compilation messages can be queried.
TEST_F(ShaderModuleValidationTest,GetCompilationMessages)160 TEST_F(ShaderModuleValidationTest, GetCompilationMessages) {
161 // This test works assuming ShaderModule is backed by a dawn_native::ShaderModuleBase, which
162 // is not the case on the wire.
163 DAWN_SKIP_TEST_IF(UsesWire());
164
165 wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, R"(
166 [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
167 return vec4<f32>(0.0, 1.0, 0.0, 1.0);
168 })");
169
170 dawn_native::ShaderModuleBase* shaderModuleBase = dawn_native::FromAPI(shaderModule.Get());
171 dawn_native::OwnedCompilationMessages* messages = shaderModuleBase->GetCompilationMessages();
172 messages->ClearMessages();
173 messages->AddMessageForTesting("Info Message");
174 messages->AddMessageForTesting("Warning Message", wgpu::CompilationMessageType::Warning);
175 messages->AddMessageForTesting("Error Message", wgpu::CompilationMessageType::Error, 3, 4);
176 messages->AddMessageForTesting("Complete Message", wgpu::CompilationMessageType::Info, 3, 4, 5,
177 6);
178
179 auto callback = [](WGPUCompilationInfoRequestStatus status, const WGPUCompilationInfo* info,
180 void* userdata) {
181 ASSERT_EQ(WGPUCompilationInfoRequestStatus_Success, status);
182 ASSERT_NE(nullptr, info);
183 ASSERT_EQ(4u, info->messageCount);
184
185 const WGPUCompilationMessage* message = &info->messages[0];
186 ASSERT_STREQ("Info Message", message->message);
187 ASSERT_EQ(WGPUCompilationMessageType_Info, message->type);
188 ASSERT_EQ(0u, message->lineNum);
189 ASSERT_EQ(0u, message->linePos);
190
191 message = &info->messages[1];
192 ASSERT_STREQ("Warning Message", message->message);
193 ASSERT_EQ(WGPUCompilationMessageType_Warning, message->type);
194 ASSERT_EQ(0u, message->lineNum);
195 ASSERT_EQ(0u, message->linePos);
196
197 message = &info->messages[2];
198 ASSERT_STREQ("Error Message", message->message);
199 ASSERT_EQ(WGPUCompilationMessageType_Error, message->type);
200 ASSERT_EQ(3u, message->lineNum);
201 ASSERT_EQ(4u, message->linePos);
202
203 message = &info->messages[3];
204 ASSERT_STREQ("Complete Message", message->message);
205 ASSERT_EQ(WGPUCompilationMessageType_Info, message->type);
206 ASSERT_EQ(3u, message->lineNum);
207 ASSERT_EQ(4u, message->linePos);
208 ASSERT_EQ(5u, message->offset);
209 ASSERT_EQ(6u, message->length);
210 };
211
212 shaderModule.GetCompilationInfo(callback, nullptr);
213 }
214
215 // Validate the maximum location of effective inter-stage variables cannot be greater than 14
216 // (kMaxInterStageShaderComponents / 4 - 1).
TEST_F(ShaderModuleValidationTest,MaximumShaderIOLocations)217 TEST_F(ShaderModuleValidationTest, MaximumShaderIOLocations) {
218 auto generateShaderForTest = [](uint32_t maximumOutputLocation, wgpu::ShaderStage shaderStage) {
219 std::ostringstream stream;
220 stream << "struct ShaderIO {" << std::endl;
221 for (uint32_t location = 1; location <= maximumOutputLocation; ++location) {
222 stream << "[[location(" << location << ")]] var" << location << ": f32;" << std::endl;
223 }
224 switch (shaderStage) {
225 case wgpu::ShaderStage::Vertex: {
226 stream << R"(
227 [[builtin(position)]] pos: vec4<f32>;
228 };
229 [[stage(vertex)]] fn main() -> ShaderIO {
230 var shaderIO : ShaderIO;
231 shaderIO.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
232 return shaderIO;
233 })";
234 } break;
235
236 case wgpu::ShaderStage::Fragment: {
237 stream << R"(
238 };
239 [[stage(fragment)]] fn main(shaderIO: ShaderIO) -> [[location(0)]] vec4<f32> {
240 return vec4<f32>(0.0, 0.0, 0.0, 1.0);
241 })";
242 } break;
243
244 case wgpu::ShaderStage::Compute:
245 default:
246 UNREACHABLE();
247 }
248
249 return stream.str();
250 };
251
252 constexpr uint32_t kMaxInterShaderIOLocation = kMaxInterStageShaderComponents / 4 - 1;
253
254 // It is allowed to create a shader module with the maximum active vertex output location == 14;
255 {
256 std::string vertexShader =
257 generateShaderForTest(kMaxInterShaderIOLocation, wgpu::ShaderStage::Vertex);
258 utils::CreateShaderModule(device, vertexShader.c_str());
259 }
260
261 // It isn't allowed to create a shader module with the maximum active vertex output location >
262 // 14;
263 {
264 std::string vertexShader =
265 generateShaderForTest(kMaxInterShaderIOLocation + 1, wgpu::ShaderStage::Vertex);
266 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, vertexShader.c_str()));
267 }
268
269 // It is allowed to create a shader module with the maximum active fragment input location ==
270 // 14;
271 {
272 std::string fragmentShader =
273 generateShaderForTest(kMaxInterShaderIOLocation, wgpu::ShaderStage::Fragment);
274 utils::CreateShaderModule(device, fragmentShader.c_str());
275 }
276
277 // It is allowed to create a shader module with the maximum active vertex output location > 14;
278 {
279 std::string fragmentShader =
280 generateShaderForTest(kMaxInterShaderIOLocation + 1, wgpu::ShaderStage::Fragment);
281 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
282 }
283 }
284
285 // Validate the maximum number of total inter-stage user-defined variable component count and
286 // built-in variables cannot exceed kMaxInterStageShaderComponents.
TEST_F(ShaderModuleValidationTest,MaximumInterStageShaderComponents)287 TEST_F(ShaderModuleValidationTest, MaximumInterStageShaderComponents) {
288 auto generateShaderForTest = [](uint32_t totalUserDefinedInterStageShaderComponentCount,
289 wgpu::ShaderStage shaderStage,
290 const char* builtInDeclarations) {
291 std::ostringstream stream;
292 stream << "struct ShaderIO {" << std::endl << builtInDeclarations << std::endl;
293 uint32_t vec4InputLocations = totalUserDefinedInterStageShaderComponentCount / 4;
294
295 for (uint32_t location = 0; location < vec4InputLocations; ++location) {
296 stream << "[[location(" << location << ")]] var" << location << ": vec4<f32>;"
297 << std::endl;
298 }
299
300 uint32_t lastComponentCount = totalUserDefinedInterStageShaderComponentCount % 4;
301 if (lastComponentCount > 0) {
302 stream << "[[location(" << vec4InputLocations << ")]] var" << vec4InputLocations
303 << ": ";
304 if (lastComponentCount == 1) {
305 stream << "f32;";
306 } else {
307 stream << " vec" << lastComponentCount << "<f32>;";
308 }
309 stream << std::endl;
310 }
311
312 switch (shaderStage) {
313 case wgpu::ShaderStage::Vertex: {
314 stream << R"(
315 [[builtin(position)]] pos: vec4<f32>;
316 };
317 [[stage(vertex)]] fn main() -> ShaderIO {
318 var shaderIO : ShaderIO;
319 shaderIO.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
320 return shaderIO;
321 })";
322 } break;
323
324 case wgpu::ShaderStage::Fragment: {
325 stream << R"(
326 };
327 [[stage(fragment)]] fn main(shaderIO: ShaderIO) -> [[location(0)]] vec4<f32> {
328 return vec4<f32>(0.0, 0.0, 0.0, 1.0);
329 })";
330 } break;
331
332 case wgpu::ShaderStage::Compute:
333 default:
334 UNREACHABLE();
335 }
336
337 return stream.str();
338 };
339
340 // Verify when there is no input builtin variable in a fragment shader, the total user-defined
341 // input component count must be less than kMaxInterStageShaderComponents.
342 {
343 constexpr uint32_t kInterStageShaderComponentCount = kMaxInterStageShaderComponents;
344 std::string correctFragmentShader =
345 generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Fragment, "");
346 utils::CreateShaderModule(device, correctFragmentShader.c_str());
347
348 std::string errorFragmentShader = generateShaderForTest(kInterStageShaderComponentCount + 1,
349 wgpu::ShaderStage::Fragment, "");
350 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, errorFragmentShader.c_str()));
351 }
352
353 // [[position]] should be counted into the maximum inter-stage component count.
354 // Note that in vertex shader we always have [[position]] so we don't need to specify it
355 // again in the parameter "builtInDeclarations" of generateShaderForTest().
356 {
357 constexpr uint32_t kInterStageShaderComponentCount = kMaxInterStageShaderComponents - 4;
358 std::string vertexShader =
359 generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Vertex, "");
360 utils::CreateShaderModule(device, vertexShader.c_str());
361
362 std::string fragmentShader =
363 generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Fragment,
364 "[[builtin(position)]] fragCoord: vec4<f32>;");
365 utils::CreateShaderModule(device, fragmentShader.c_str());
366 }
367
368 {
369 constexpr uint32_t kInterStageShaderComponentCount = kMaxInterStageShaderComponents - 3;
370 std::string vertexShader =
371 generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Vertex, "");
372 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, vertexShader.c_str()));
373
374 std::string fragmentShader =
375 generateShaderForTest(kInterStageShaderComponentCount, wgpu::ShaderStage::Fragment,
376 "[[builtin(position)]] fragCoord: vec4<f32>;");
377 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
378 }
379
380 // [[front_facing]] should be counted into the maximum inter-stage component count.
381 {
382 const char* builtinDeclaration = "[[builtin(front_facing)]] frontFacing : bool;";
383
384 {
385 std::string fragmentShader =
386 generateShaderForTest(kMaxInterStageShaderComponents - 1,
387 wgpu::ShaderStage::Fragment, builtinDeclaration);
388 utils::CreateShaderModule(device, fragmentShader.c_str());
389 }
390
391 {
392 std::string fragmentShader = generateShaderForTest(
393 kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment, builtinDeclaration);
394 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
395 }
396 }
397
398 // [[sample_index]] should be counted into the maximum inter-stage component count.
399 {
400 const char* builtinDeclaration = "[[builtin(sample_index)]] sampleIndex: u32;";
401
402 {
403 std::string fragmentShader =
404 generateShaderForTest(kMaxInterStageShaderComponents - 1,
405 wgpu::ShaderStage::Fragment, builtinDeclaration);
406 utils::CreateShaderModule(device, fragmentShader.c_str());
407 }
408
409 {
410 std::string fragmentShader = generateShaderForTest(
411 kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment, builtinDeclaration);
412 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
413 }
414 }
415
416 // [[sample_mask]] should be counted into the maximum inter-stage component count.
417 {
418 const char* builtinDeclaration = "[[builtin(front_facing)]] frontFacing : bool;";
419
420 {
421 std::string fragmentShader =
422 generateShaderForTest(kMaxInterStageShaderComponents - 1,
423 wgpu::ShaderStage::Fragment, builtinDeclaration);
424 utils::CreateShaderModule(device, fragmentShader.c_str());
425 }
426
427 {
428 std::string fragmentShader = generateShaderForTest(
429 kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment, builtinDeclaration);
430 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, fragmentShader.c_str()));
431 }
432 }
433 }
434
435 // Tests that we validate workgroup size limits.
TEST_F(ShaderModuleValidationTest,ComputeWorkgroupSizeLimits)436 TEST_F(ShaderModuleValidationTest, ComputeWorkgroupSizeLimits) {
437 auto MakeShaderWithWorkgroupSize = [this](uint32_t x, uint32_t y, uint32_t z) {
438 std::ostringstream ss;
439 ss << "[[stage(compute), workgroup_size(" << x << "," << y << "," << z
440 << ")]] fn main() {}";
441 utils::CreateShaderModule(device, ss.str().c_str());
442 };
443
444 wgpu::Limits supportedLimits = GetSupportedLimits().limits;
445
446 MakeShaderWithWorkgroupSize(1, 1, 1);
447 MakeShaderWithWorkgroupSize(supportedLimits.maxComputeWorkgroupSizeX, 1, 1);
448 MakeShaderWithWorkgroupSize(1, supportedLimits.maxComputeWorkgroupSizeY, 1);
449 MakeShaderWithWorkgroupSize(1, 1, supportedLimits.maxComputeWorkgroupSizeZ);
450
451 ASSERT_DEVICE_ERROR(
452 MakeShaderWithWorkgroupSize(supportedLimits.maxComputeWorkgroupSizeX + 1, 1, 1));
453 ASSERT_DEVICE_ERROR(
454 MakeShaderWithWorkgroupSize(1, supportedLimits.maxComputeWorkgroupSizeY + 1, 1));
455 ASSERT_DEVICE_ERROR(
456 MakeShaderWithWorkgroupSize(1, 1, supportedLimits.maxComputeWorkgroupSizeZ + 1));
457
458 // No individual dimension exceeds its limit, but the combined size should definitely exceed the
459 // total invocation limit.
460 ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupSize(supportedLimits.maxComputeWorkgroupSizeX,
461 supportedLimits.maxComputeWorkgroupSizeY,
462 supportedLimits.maxComputeWorkgroupSizeZ));
463 }
464
465 // Tests that we validate workgroup storage size limits.
TEST_F(ShaderModuleValidationTest,ComputeWorkgroupStorageSizeLimits)466 TEST_F(ShaderModuleValidationTest, ComputeWorkgroupStorageSizeLimits) {
467 wgpu::Limits supportedLimits = GetSupportedLimits().limits;
468
469 constexpr uint32_t kVec4Size = 16;
470 const uint32_t maxVec4Count = supportedLimits.maxComputeWorkgroupStorageSize / kVec4Size;
471 constexpr uint32_t kMat4Size = 64;
472 const uint32_t maxMat4Count = supportedLimits.maxComputeWorkgroupStorageSize / kMat4Size;
473
474 auto MakeShaderWithWorkgroupStorage = [this](uint32_t vec4_count, uint32_t mat4_count) {
475 std::ostringstream ss;
476 std::ostringstream body;
477 if (vec4_count > 0) {
478 ss << "var<workgroup> vec4_data: array<vec4<f32>, " << vec4_count << ">;";
479 body << "_ = vec4_data;";
480 }
481 if (mat4_count > 0) {
482 ss << "var<workgroup> mat4_data: array<mat4x4<f32>, " << mat4_count << ">;";
483 body << "_ = mat4_data;";
484 }
485 ss << "[[stage(compute), workgroup_size(1)]] fn main() { " << body.str() << " }";
486 utils::CreateShaderModule(device, ss.str().c_str());
487 };
488
489 MakeShaderWithWorkgroupStorage(1, 1);
490 MakeShaderWithWorkgroupStorage(maxVec4Count, 0);
491 MakeShaderWithWorkgroupStorage(0, maxMat4Count);
492 MakeShaderWithWorkgroupStorage(maxVec4Count - 4, 1);
493 MakeShaderWithWorkgroupStorage(4, maxMat4Count - 1);
494 ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupStorage(maxVec4Count + 1, 0));
495 ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupStorage(maxVec4Count - 3, 1));
496 ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupStorage(0, maxMat4Count + 1));
497 ASSERT_DEVICE_ERROR(MakeShaderWithWorkgroupStorage(4, maxMat4Count));
498 }
499
500 // Test that numeric ID must be unique
TEST_F(ShaderModuleValidationTest,OverridableConstantsNumericIDConflicts)501 TEST_F(ShaderModuleValidationTest, OverridableConstantsNumericIDConflicts) {
502 ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
503 [[override(1234)]] let c0: u32;
504 [[override(1234)]] let c1: u32;
505
506 [[block]] struct Buf {
507 data : array<u32, 2>;
508 };
509
510 [[group(0), binding(0)]] var<storage, read_write> buf : Buf;
511
512 [[stage(compute), workgroup_size(1)]] fn main() {
513 // make sure the overridable constants are not optimized out
514 buf.data[0] = c0;
515 buf.data[1] = c1;
516 })"));
517 }
518