• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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