• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 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 "tests/unittests/validation/ValidationTest.h"
16 
17 #include "common/Constants.h"
18 #include "utils/ComboRenderPipelineDescriptor.h"
19 #include "utils/WGPUHelpers.h"
20 
21 #include <cmath>
22 #include <sstream>
23 
24 class RenderPipelineValidationTest : public ValidationTest {
25   protected:
SetUp()26     void SetUp() override {
27         ValidationTest::SetUp();
28 
29         vsModule = utils::CreateShaderModule(device, R"(
30             [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {
31                 return vec4<f32>(0.0, 0.0, 0.0, 1.0);
32             })");
33 
34         fsModule = utils::CreateShaderModule(device, R"(
35             [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
36                 return vec4<f32>(0.0, 1.0, 0.0, 1.0);
37             })");
38 
39         fsModuleUint = utils::CreateShaderModule(device, R"(
40             [[stage(fragment)]] fn main() -> [[location(0)]] vec4<u32> {
41                 return vec4<u32>(0u, 255u, 0u, 255u);
42             })");
43     }
44 
45     wgpu::ShaderModule vsModule;
46     wgpu::ShaderModule fsModule;
47     wgpu::ShaderModule fsModuleUint;
48 };
49 
50 namespace {
BlendFactorContainsSrcAlpha(const wgpu::BlendFactor & blendFactor)51     bool BlendFactorContainsSrcAlpha(const wgpu::BlendFactor& blendFactor) {
52         return blendFactor == wgpu::BlendFactor::SrcAlpha ||
53                blendFactor == wgpu::BlendFactor::OneMinusSrcAlpha ||
54                blendFactor == wgpu::BlendFactor::SrcAlphaSaturated;
55     }
56 }  // namespace
57 
58 // Test cases where creation should succeed
TEST_F(RenderPipelineValidationTest,CreationSuccess)59 TEST_F(RenderPipelineValidationTest, CreationSuccess) {
60     {
61         // New format
62         utils::ComboRenderPipelineDescriptor descriptor;
63         descriptor.vertex.module = vsModule;
64         descriptor.cFragment.module = fsModule;
65 
66         device.CreateRenderPipeline(&descriptor);
67     }
68 }
69 
70 // Tests that depth bias parameters must not be NaN.
TEST_F(RenderPipelineValidationTest,DepthBiasParameterNotBeNaN)71 TEST_F(RenderPipelineValidationTest, DepthBiasParameterNotBeNaN) {
72     // Control case, depth bias parameters in ComboRenderPipeline default to 0 which is finite
73     {
74         utils::ComboRenderPipelineDescriptor descriptor;
75         descriptor.vertex.module = vsModule;
76         descriptor.cFragment.module = fsModule;
77         descriptor.EnableDepthStencil();
78         device.CreateRenderPipeline(&descriptor);
79     }
80 
81     // Infinite depth bias clamp is valid
82     {
83         utils::ComboRenderPipelineDescriptor descriptor;
84         descriptor.vertex.module = vsModule;
85         descriptor.cFragment.module = fsModule;
86         wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil();
87         depthStencil->depthBiasClamp = INFINITY;
88         device.CreateRenderPipeline(&descriptor);
89     }
90     // NAN depth bias clamp is invalid
91     {
92         utils::ComboRenderPipelineDescriptor descriptor;
93         descriptor.vertex.module = vsModule;
94         descriptor.cFragment.module = fsModule;
95         wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil();
96         depthStencil->depthBiasClamp = NAN;
97         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
98     }
99 
100     // Infinite depth bias slope is valid
101     {
102         utils::ComboRenderPipelineDescriptor descriptor;
103         descriptor.vertex.module = vsModule;
104         descriptor.cFragment.module = fsModule;
105         wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil();
106         depthStencil->depthBiasSlopeScale = INFINITY;
107         device.CreateRenderPipeline(&descriptor);
108     }
109     // NAN depth bias slope is invalid
110     {
111         utils::ComboRenderPipelineDescriptor descriptor;
112         descriptor.vertex.module = vsModule;
113         descriptor.cFragment.module = fsModule;
114         wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil();
115         depthStencil->depthBiasSlopeScale = NAN;
116         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
117     }
118 }
119 
120 // Tests that depth or stencil aspect is required if we enable depth or stencil test.
TEST_F(RenderPipelineValidationTest,DepthStencilAspectRequirement)121 TEST_F(RenderPipelineValidationTest, DepthStencilAspectRequirement) {
122     // Control case, stencil aspect is required if stencil test or stencil write is enabled
123     {
124         utils::ComboRenderPipelineDescriptor descriptor;
125         descriptor.vertex.module = vsModule;
126         descriptor.cFragment.module = fsModule;
127         wgpu::DepthStencilState* depthStencil =
128             descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24PlusStencil8);
129         depthStencil->stencilFront.compare = wgpu::CompareFunction::LessEqual;
130         depthStencil->stencilBack.failOp = wgpu::StencilOperation::Replace;
131         device.CreateRenderPipeline(&descriptor);
132     }
133 
134     // It is invalid if the texture format doesn't have stencil aspect while stencil test is
135     // enabled (depthStencilState.stencilFront are not default values).
136     {
137         utils::ComboRenderPipelineDescriptor descriptor;
138         descriptor.vertex.module = vsModule;
139         descriptor.cFragment.module = fsModule;
140         wgpu::DepthStencilState* depthStencil =
141             descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus);
142         depthStencil->stencilFront.compare = wgpu::CompareFunction::LessEqual;
143         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
144     }
145 
146     // It is invalid if the texture format doesn't have stencil aspect while stencil write is
147     // enabled (depthStencilState.stencilBack are not default values).
148     {
149         utils::ComboRenderPipelineDescriptor descriptor;
150         descriptor.vertex.module = vsModule;
151         descriptor.cFragment.module = fsModule;
152         wgpu::DepthStencilState* depthStencil =
153             descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus);
154         depthStencil->stencilBack.failOp = wgpu::StencilOperation::Replace;
155         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
156     }
157 
158     // Control case, depth aspect is required if depth test or depth write is enabled
159     {
160         utils::ComboRenderPipelineDescriptor descriptor;
161         descriptor.vertex.module = vsModule;
162         descriptor.cFragment.module = fsModule;
163         wgpu::DepthStencilState* depthStencil =
164             descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24PlusStencil8);
165         depthStencil->depthCompare = wgpu::CompareFunction::LessEqual;
166         depthStencil->depthWriteEnabled = true;
167         device.CreateRenderPipeline(&descriptor);
168     }
169 
170     // TODO(dawn:666): Add tests for stencil-only format (Stencil8) with depth test or depth write
171     // enabled when Stencil8 format is implemented
172 }
173 
174 // Tests that at least one color target state is required.
TEST_F(RenderPipelineValidationTest,ColorTargetStateRequired)175 TEST_F(RenderPipelineValidationTest, ColorTargetStateRequired) {
176     {
177         // This one succeeds because attachment 0 is the color attachment
178         utils::ComboRenderPipelineDescriptor descriptor;
179         descriptor.vertex.module = vsModule;
180         descriptor.cFragment.module = fsModule;
181         descriptor.cFragment.targetCount = 1;
182 
183         device.CreateRenderPipeline(&descriptor);
184     }
185 
186     {  // Fail because lack of color target states (and depth/stencil state)
187         utils::ComboRenderPipelineDescriptor descriptor;
188         descriptor.vertex.module = vsModule;
189         descriptor.cFragment.module = fsModule;
190         descriptor.cFragment.targetCount = 0;
191 
192         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
193     }
194 }
195 
196 // Tests that the color formats must be renderable.
TEST_F(RenderPipelineValidationTest,NonRenderableFormat)197 TEST_F(RenderPipelineValidationTest, NonRenderableFormat) {
198     {
199         // Succeeds because RGBA8Unorm is renderable
200         utils::ComboRenderPipelineDescriptor descriptor;
201         descriptor.vertex.module = vsModule;
202         descriptor.cFragment.module = fsModule;
203         descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
204 
205         device.CreateRenderPipeline(&descriptor);
206     }
207 
208     {
209         // Fails because RG11B10Ufloat is non-renderable
210         utils::ComboRenderPipelineDescriptor descriptor;
211         descriptor.vertex.module = vsModule;
212         descriptor.cFragment.module = fsModule;
213         descriptor.cTargets[0].format = wgpu::TextureFormat::RG11B10Ufloat;
214 
215         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
216     }
217 }
218 
219 // Tests that the color formats must be blendable when blending is enabled.
220 // Those are renderable color formats with "float" capabilities in
221 // https://gpuweb.github.io/gpuweb/#plain-color-formats
TEST_F(RenderPipelineValidationTest,NonBlendableFormat)222 TEST_F(RenderPipelineValidationTest, NonBlendableFormat) {
223     {
224         // Succeeds because RGBA8Unorm is blendable
225         utils::ComboRenderPipelineDescriptor descriptor;
226         descriptor.vertex.module = vsModule;
227         descriptor.cFragment.module = fsModule;
228         descriptor.cTargets[0].blend = &descriptor.cBlends[0];
229         descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
230 
231         device.CreateRenderPipeline(&descriptor);
232     }
233 
234     {
235         // Fails because RGBA32Float is not blendable
236         utils::ComboRenderPipelineDescriptor descriptor;
237         descriptor.vertex.module = vsModule;
238         descriptor.cFragment.module = fsModule;
239         descriptor.cTargets[0].blend = &descriptor.cBlends[0];
240         descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA32Float;
241 
242         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
243     }
244 
245     {
246         // Succeeds because RGBA32Float is not blendable but blending is disabled
247         utils::ComboRenderPipelineDescriptor descriptor;
248         descriptor.vertex.module = vsModule;
249         descriptor.cFragment.module = fsModule;
250         descriptor.cTargets[0].blend = nullptr;
251         descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA32Float;
252 
253         device.CreateRenderPipeline(&descriptor);
254     }
255 
256     {
257         // Fails because RGBA8Uint is not blendable
258         utils::ComboRenderPipelineDescriptor descriptor;
259         descriptor.vertex.module = vsModule;
260         descriptor.cFragment.module = fsModuleUint;
261         descriptor.cTargets[0].blend = &descriptor.cBlends[0];
262         descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint;
263 
264         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
265     }
266 
267     {
268         // Succeeds because RGBA8Uint is not blendable but blending is disabled
269         utils::ComboRenderPipelineDescriptor descriptor;
270         descriptor.vertex.module = vsModule;
271         descriptor.cFragment.module = fsModuleUint;
272         descriptor.cTargets[0].blend = nullptr;
273         descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Uint;
274 
275         device.CreateRenderPipeline(&descriptor);
276     }
277 }
278 
279 // Tests that the format of the color state descriptor must match the output of the fragment shader.
TEST_F(RenderPipelineValidationTest,FragmentOutputFormatCompatibility)280 TEST_F(RenderPipelineValidationTest, FragmentOutputFormatCompatibility) {
281     std::array<const char*, 3> kScalarTypes = {{"f32", "i32", "u32"}};
282     std::array<wgpu::TextureFormat, 3> kColorFormats = {{wgpu::TextureFormat::RGBA8Unorm,
283                                                          wgpu::TextureFormat::RGBA8Sint,
284                                                          wgpu::TextureFormat::RGBA8Uint}};
285 
286     for (size_t i = 0; i < kScalarTypes.size(); ++i) {
287         utils::ComboRenderPipelineDescriptor descriptor;
288         descriptor.vertex.module = vsModule;
289         std::ostringstream stream;
290         stream << R"(
291             [[stage(fragment)]] fn main() -> [[location(0)]] vec4<)"
292                << kScalarTypes[i] << R"(> {
293                 var result : vec4<)"
294                << kScalarTypes[i] << R"(>;
295                 return result;
296             })";
297         descriptor.cFragment.module = utils::CreateShaderModule(device, stream.str().c_str());
298 
299         for (size_t j = 0; j < kColorFormats.size(); ++j) {
300             descriptor.cTargets[0].format = kColorFormats[j];
301             if (i == j) {
302                 device.CreateRenderPipeline(&descriptor);
303             } else {
304                 ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
305             }
306         }
307     }
308 }
309 
310 // Tests that the component count of the color state target format must be fewer than that of the
311 // fragment shader output.
TEST_F(RenderPipelineValidationTest,FragmentOutputComponentCountCompatibility)312 TEST_F(RenderPipelineValidationTest, FragmentOutputComponentCountCompatibility) {
313     std::array<wgpu::TextureFormat, 3> kColorFormats = {wgpu::TextureFormat::R8Unorm,
314                                                         wgpu::TextureFormat::RG8Unorm,
315                                                         wgpu::TextureFormat::RGBA8Unorm};
316 
317     std::array<wgpu::BlendFactor, 8> kBlendFactors = {wgpu::BlendFactor::Zero,
318                                                       wgpu::BlendFactor::One,
319                                                       wgpu::BlendFactor::SrcAlpha,
320                                                       wgpu::BlendFactor::OneMinusSrcAlpha,
321                                                       wgpu::BlendFactor::Src,
322                                                       wgpu::BlendFactor::DstAlpha,
323                                                       wgpu::BlendFactor::OneMinusDstAlpha,
324                                                       wgpu::BlendFactor::Dst};
325 
326     for (size_t componentCount = 1; componentCount <= 4; ++componentCount) {
327         utils::ComboRenderPipelineDescriptor descriptor;
328         descriptor.vertex.module = vsModule;
329 
330         std::ostringstream stream;
331         stream << R"(
332             [[stage(fragment)]] fn main() -> [[location(0)]] )";
333         switch (componentCount) {
334             case 1:
335                 stream << R"(f32 {
336                 return 1.0;
337                 })";
338                 break;
339             case 2:
340                 stream << R"(vec2<f32> {
341                 return vec2<f32>(1.0, 1.0);
342                 })";
343                 break;
344             case 3:
345                 stream << R"(vec3<f32> {
346                 return vec3<f32>(1.0, 1.0, 1.0);
347                 })";
348                 break;
349             case 4:
350                 stream << R"(vec4<f32> {
351                 return vec4<f32>(1.0, 1.0, 1.0, 1.0);
352                 })";
353                 break;
354             default:
355                 UNREACHABLE();
356         }
357         descriptor.cFragment.module = utils::CreateShaderModule(device, stream.str().c_str());
358 
359         for (auto colorFormat : kColorFormats) {
360             descriptor.cTargets[0].format = colorFormat;
361 
362             descriptor.cTargets[0].blend = nullptr;
363             if (componentCount >= utils::GetWGSLRenderableColorTextureComponentCount(colorFormat)) {
364                 device.CreateRenderPipeline(&descriptor);
365             } else {
366                 ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
367             }
368 
369             descriptor.cTargets[0].blend = &descriptor.cBlends[0];
370 
371             for (auto colorSrcFactor : kBlendFactors) {
372                 descriptor.cBlends[0].color.srcFactor = colorSrcFactor;
373                 for (auto colorDstFactor : kBlendFactors) {
374                     descriptor.cBlends[0].color.dstFactor = colorDstFactor;
375                     for (auto alphaSrcFactor : kBlendFactors) {
376                         descriptor.cBlends[0].alpha.srcFactor = alphaSrcFactor;
377                         for (auto alphaDstFactor : kBlendFactors) {
378                             descriptor.cBlends[0].alpha.dstFactor = alphaDstFactor;
379 
380                             bool valid = true;
381                             if (componentCount >=
382                                 utils::GetWGSLRenderableColorTextureComponentCount(colorFormat)) {
383                                 if (BlendFactorContainsSrcAlpha(
384                                         descriptor.cTargets[0].blend->color.srcFactor) ||
385                                     BlendFactorContainsSrcAlpha(
386                                         descriptor.cTargets[0].blend->color.dstFactor)) {
387                                     valid = componentCount == 4;
388                                 }
389                             } else {
390                                 valid = false;
391                             }
392 
393                             if (valid) {
394                                 device.CreateRenderPipeline(&descriptor);
395                             } else {
396                                 ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
397                             }
398                         }
399                     }
400                 }
401             }
402         }
403     }
404 }
405 
406 /// Tests that the sample count of the render pipeline must be valid.
TEST_F(RenderPipelineValidationTest,SampleCount)407 TEST_F(RenderPipelineValidationTest, SampleCount) {
408     {
409         utils::ComboRenderPipelineDescriptor descriptor;
410         descriptor.vertex.module = vsModule;
411         descriptor.cFragment.module = fsModule;
412         descriptor.multisample.count = 4;
413 
414         device.CreateRenderPipeline(&descriptor);
415     }
416 
417     {
418         utils::ComboRenderPipelineDescriptor descriptor;
419         descriptor.vertex.module = vsModule;
420         descriptor.cFragment.module = fsModule;
421         descriptor.multisample.count = 3;
422 
423         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
424     }
425 }
426 
427 // Tests that the sample count of the render pipeline must be equal to the one of every attachments
428 // in the render pass.
TEST_F(RenderPipelineValidationTest,SampleCountCompatibilityWithRenderPass)429 TEST_F(RenderPipelineValidationTest, SampleCountCompatibilityWithRenderPass) {
430     constexpr uint32_t kMultisampledCount = 4;
431     constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
432     constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
433 
434     wgpu::TextureDescriptor baseTextureDescriptor;
435     baseTextureDescriptor.size.width = 4;
436     baseTextureDescriptor.size.height = 4;
437     baseTextureDescriptor.size.depthOrArrayLayers = 1;
438     baseTextureDescriptor.mipLevelCount = 1;
439     baseTextureDescriptor.dimension = wgpu::TextureDimension::e2D;
440     baseTextureDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
441 
442     utils::ComboRenderPipelineDescriptor nonMultisampledPipelineDescriptor;
443     nonMultisampledPipelineDescriptor.multisample.count = 1;
444     nonMultisampledPipelineDescriptor.vertex.module = vsModule;
445     nonMultisampledPipelineDescriptor.cFragment.module = fsModule;
446     wgpu::RenderPipeline nonMultisampledPipeline =
447         device.CreateRenderPipeline(&nonMultisampledPipelineDescriptor);
448 
449     nonMultisampledPipelineDescriptor.cFragment.targetCount = 0;
450     nonMultisampledPipelineDescriptor.EnableDepthStencil();
451     wgpu::RenderPipeline nonMultisampledPipelineWithDepthStencilOnly =
452         device.CreateRenderPipeline(&nonMultisampledPipelineDescriptor);
453 
454     utils::ComboRenderPipelineDescriptor multisampledPipelineDescriptor;
455     multisampledPipelineDescriptor.multisample.count = kMultisampledCount;
456     multisampledPipelineDescriptor.vertex.module = vsModule;
457     multisampledPipelineDescriptor.cFragment.module = fsModule;
458     wgpu::RenderPipeline multisampledPipeline =
459         device.CreateRenderPipeline(&multisampledPipelineDescriptor);
460 
461     multisampledPipelineDescriptor.cFragment.targetCount = 0;
462     multisampledPipelineDescriptor.EnableDepthStencil();
463     wgpu::RenderPipeline multisampledPipelineWithDepthStencilOnly =
464         device.CreateRenderPipeline(&multisampledPipelineDescriptor);
465 
466     // It is not allowed to use multisampled render pass and non-multisampled render pipeline.
467     {
468         wgpu::TextureDescriptor textureDescriptor = baseTextureDescriptor;
469         textureDescriptor.format = kColorFormat;
470         textureDescriptor.sampleCount = kMultisampledCount;
471         wgpu::Texture multisampledColorTexture = device.CreateTexture(&textureDescriptor);
472         utils::ComboRenderPassDescriptor renderPassDescriptor(
473             {multisampledColorTexture.CreateView()});
474 
475         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
476         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
477         renderPass.SetPipeline(nonMultisampledPipeline);
478         renderPass.EndPass();
479 
480         ASSERT_DEVICE_ERROR(encoder.Finish());
481     }
482 
483     {
484         wgpu::TextureDescriptor textureDescriptor = baseTextureDescriptor;
485         textureDescriptor.sampleCount = kMultisampledCount;
486         textureDescriptor.format = kDepthStencilFormat;
487         wgpu::Texture multisampledDepthStencilTexture = device.CreateTexture(&textureDescriptor);
488         utils::ComboRenderPassDescriptor renderPassDescriptor(
489             {}, multisampledDepthStencilTexture.CreateView());
490 
491         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
492         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
493         renderPass.SetPipeline(nonMultisampledPipelineWithDepthStencilOnly);
494         renderPass.EndPass();
495 
496         ASSERT_DEVICE_ERROR(encoder.Finish());
497     }
498 
499     // It is allowed to use multisampled render pass and multisampled render pipeline.
500     {
501         wgpu::TextureDescriptor textureDescriptor = baseTextureDescriptor;
502         textureDescriptor.format = kColorFormat;
503         textureDescriptor.sampleCount = kMultisampledCount;
504         wgpu::Texture multisampledColorTexture = device.CreateTexture(&textureDescriptor);
505         utils::ComboRenderPassDescriptor renderPassDescriptor(
506             {multisampledColorTexture.CreateView()});
507 
508         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
509         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
510         renderPass.SetPipeline(multisampledPipeline);
511         renderPass.EndPass();
512 
513         encoder.Finish();
514     }
515 
516     {
517         wgpu::TextureDescriptor textureDescriptor = baseTextureDescriptor;
518         textureDescriptor.sampleCount = kMultisampledCount;
519         textureDescriptor.format = kDepthStencilFormat;
520         wgpu::Texture multisampledDepthStencilTexture = device.CreateTexture(&textureDescriptor);
521         utils::ComboRenderPassDescriptor renderPassDescriptor(
522             {}, multisampledDepthStencilTexture.CreateView());
523 
524         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
525         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
526         renderPass.SetPipeline(multisampledPipelineWithDepthStencilOnly);
527         renderPass.EndPass();
528 
529         encoder.Finish();
530     }
531 
532     // It is not allowed to use non-multisampled render pass and multisampled render pipeline.
533     {
534         wgpu::TextureDescriptor textureDescriptor = baseTextureDescriptor;
535         textureDescriptor.format = kColorFormat;
536         textureDescriptor.sampleCount = 1;
537         wgpu::Texture nonMultisampledColorTexture = device.CreateTexture(&textureDescriptor);
538         utils::ComboRenderPassDescriptor nonMultisampledRenderPassDescriptor(
539             {nonMultisampledColorTexture.CreateView()});
540 
541         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
542         wgpu::RenderPassEncoder renderPass =
543             encoder.BeginRenderPass(&nonMultisampledRenderPassDescriptor);
544         renderPass.SetPipeline(multisampledPipeline);
545         renderPass.EndPass();
546 
547         ASSERT_DEVICE_ERROR(encoder.Finish());
548     }
549 
550     {
551         wgpu::TextureDescriptor textureDescriptor = baseTextureDescriptor;
552         textureDescriptor.sampleCount = 1;
553         textureDescriptor.format = kDepthStencilFormat;
554         wgpu::Texture nonMultisampledDepthStencilTexture = device.CreateTexture(&textureDescriptor);
555         utils::ComboRenderPassDescriptor renderPassDescriptor(
556             {}, nonMultisampledDepthStencilTexture.CreateView());
557 
558         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
559         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
560         renderPass.SetPipeline(multisampledPipelineWithDepthStencilOnly);
561         renderPass.EndPass();
562 
563         ASSERT_DEVICE_ERROR(encoder.Finish());
564     }
565 }
566 
567 // Tests that the vertex only pipeline must be used with a depth-stencil attachment only render pass
TEST_F(RenderPipelineValidationTest,VertexOnlyPipelineRequireDepthStencilAttachment)568 TEST_F(RenderPipelineValidationTest, VertexOnlyPipelineRequireDepthStencilAttachment) {
569     constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
570     constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
571 
572     wgpu::TextureDescriptor baseTextureDescriptor;
573     baseTextureDescriptor.size = {4, 4};
574     baseTextureDescriptor.mipLevelCount = 1;
575     baseTextureDescriptor.dimension = wgpu::TextureDimension::e2D;
576     baseTextureDescriptor.usage = wgpu::TextureUsage::RenderAttachment;
577 
578     wgpu::TextureDescriptor colorTextureDescriptor = baseTextureDescriptor;
579     colorTextureDescriptor.format = kColorFormat;
580     colorTextureDescriptor.sampleCount = 1;
581     wgpu::Texture colorTexture = device.CreateTexture(&colorTextureDescriptor);
582 
583     wgpu::TextureDescriptor depthStencilTextureDescriptor = baseTextureDescriptor;
584     depthStencilTextureDescriptor.sampleCount = 1;
585     depthStencilTextureDescriptor.format = kDepthStencilFormat;
586     wgpu::Texture depthStencilTexture = device.CreateTexture(&depthStencilTextureDescriptor);
587     utils::ComboRenderPassDescriptor renderPassDescriptor({}, depthStencilTexture.CreateView());
588 
589     utils::ComboRenderPipelineDescriptor renderPipelineDescriptor;
590     renderPipelineDescriptor.multisample.count = 1;
591     renderPipelineDescriptor.vertex.module = vsModule;
592 
593     renderPipelineDescriptor.fragment = nullptr;
594 
595     renderPipelineDescriptor.EnableDepthStencil(kDepthStencilFormat);
596 
597     wgpu::RenderPipeline vertexOnlyPipeline =
598         device.CreateRenderPipeline(&renderPipelineDescriptor);
599 
600     // Vertex-only render pipeline can work with depth stencil attachment and no color target
601     {
602         utils::ComboRenderPassDescriptor renderPassDescriptor({}, depthStencilTexture.CreateView());
603 
604         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
605         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
606         renderPass.SetPipeline(vertexOnlyPipeline);
607         renderPass.EndPass();
608 
609         encoder.Finish();
610     }
611 
612     // Vertex-only render pipeline must have a depth stencil attachment
613     {
614         utils::ComboRenderPassDescriptor renderPassDescriptor({});
615 
616         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
617         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
618         renderPass.SetPipeline(vertexOnlyPipeline);
619         renderPass.EndPass();
620 
621         ASSERT_DEVICE_ERROR(encoder.Finish());
622     }
623 
624     // Vertex-only render pipeline can not work with color target
625     {
626         utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()},
627                                                               depthStencilTexture.CreateView());
628 
629         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
630         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
631         renderPass.SetPipeline(vertexOnlyPipeline);
632         renderPass.EndPass();
633 
634         ASSERT_DEVICE_ERROR(encoder.Finish());
635     }
636 
637     // Vertex-only render pipeline can not work with color target, and must have a depth stencil
638     // attachment
639     {
640         utils::ComboRenderPassDescriptor renderPassDescriptor({colorTexture.CreateView()});
641 
642         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
643         wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
644         renderPass.SetPipeline(vertexOnlyPipeline);
645         renderPass.EndPass();
646 
647         ASSERT_DEVICE_ERROR(encoder.Finish());
648     }
649 }
650 
651 // Tests that the sample count of the render pipeline must be valid
652 // when the alphaToCoverage mode is enabled.
TEST_F(RenderPipelineValidationTest,AlphaToCoverageAndSampleCount)653 TEST_F(RenderPipelineValidationTest, AlphaToCoverageAndSampleCount) {
654     {
655         utils::ComboRenderPipelineDescriptor descriptor;
656         descriptor.vertex.module = vsModule;
657         descriptor.cFragment.module = fsModule;
658         descriptor.multisample.count = 4;
659         descriptor.multisample.alphaToCoverageEnabled = true;
660 
661         device.CreateRenderPipeline(&descriptor);
662     }
663 
664     {
665         utils::ComboRenderPipelineDescriptor descriptor;
666         descriptor.vertex.module = vsModule;
667         descriptor.cFragment.module = fsModule;
668         descriptor.multisample.count = 1;
669         descriptor.multisample.alphaToCoverageEnabled = true;
670 
671         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
672     }
673 }
674 
675 // Tests that the texture component type in shader must match the bind group layout.
TEST_F(RenderPipelineValidationTest,TextureComponentTypeCompatibility)676 TEST_F(RenderPipelineValidationTest, TextureComponentTypeCompatibility) {
677     constexpr uint32_t kNumTextureComponentType = 3u;
678     std::array<const char*, kNumTextureComponentType> kScalarTypes = {{"f32", "i32", "u32"}};
679     std::array<wgpu::TextureSampleType, kNumTextureComponentType> kTextureComponentTypes = {{
680         wgpu::TextureSampleType::Float,
681         wgpu::TextureSampleType::Sint,
682         wgpu::TextureSampleType::Uint,
683     }};
684 
685     for (size_t i = 0; i < kNumTextureComponentType; ++i) {
686         for (size_t j = 0; j < kNumTextureComponentType; ++j) {
687             utils::ComboRenderPipelineDescriptor descriptor;
688             descriptor.vertex.module = vsModule;
689 
690             std::ostringstream stream;
691             stream << R"(
692                 [[group(0), binding(0)]] var myTexture : texture_2d<)"
693                    << kScalarTypes[i] << R"(>;
694 
695                 [[stage(fragment)]] fn main() {
696                     textureDimensions(myTexture);
697                 })";
698             descriptor.cFragment.module = utils::CreateShaderModule(device, stream.str().c_str());
699             descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
700 
701             wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
702                 device, {{0, wgpu::ShaderStage::Fragment, kTextureComponentTypes[j]}});
703             descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl);
704 
705             if (i == j) {
706                 device.CreateRenderPipeline(&descriptor);
707             } else {
708                 ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
709             }
710         }
711     }
712 }
713 
714 // Tests that the texture view dimension in shader must match the bind group layout.
TEST_F(RenderPipelineValidationTest,TextureViewDimensionCompatibility)715 TEST_F(RenderPipelineValidationTest, TextureViewDimensionCompatibility) {
716     constexpr uint32_t kNumTextureViewDimensions = 6u;
717     std::array<const char*, kNumTextureViewDimensions> kTextureKeywords = {{
718         "texture_1d",
719         "texture_2d",
720         "texture_2d_array",
721         "texture_cube",
722         "texture_cube_array",
723         "texture_3d",
724     }};
725 
726     std::array<wgpu::TextureViewDimension, kNumTextureViewDimensions> kTextureViewDimensions = {{
727         wgpu::TextureViewDimension::e1D,
728         wgpu::TextureViewDimension::e2D,
729         wgpu::TextureViewDimension::e2DArray,
730         wgpu::TextureViewDimension::Cube,
731         wgpu::TextureViewDimension::CubeArray,
732         wgpu::TextureViewDimension::e3D,
733     }};
734 
735     for (size_t i = 0; i < kNumTextureViewDimensions; ++i) {
736         for (size_t j = 0; j < kNumTextureViewDimensions; ++j) {
737             utils::ComboRenderPipelineDescriptor descriptor;
738             descriptor.vertex.module = vsModule;
739 
740             std::ostringstream stream;
741             stream << R"(
742                 [[group(0), binding(0)]] var myTexture : )"
743                    << kTextureKeywords[i] << R"(<f32>;
744                 [[stage(fragment)]] fn main() {
745                     textureDimensions(myTexture);
746                 })";
747             descriptor.cFragment.module = utils::CreateShaderModule(device, stream.str().c_str());
748             descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
749 
750             wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout(
751                 device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float,
752                           kTextureViewDimensions[j]}});
753             descriptor.layout = utils::MakeBasicPipelineLayout(device, &bgl);
754 
755             if (i == j) {
756                 device.CreateRenderPipeline(&descriptor);
757             } else {
758                 ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
759             }
760         }
761     }
762 }
763 
764 // Test that declaring a storage buffer in the vertex shader without setting pipeline layout won't
765 // cause crash.
TEST_F(RenderPipelineValidationTest,StorageBufferInVertexShaderNoLayout)766 TEST_F(RenderPipelineValidationTest, StorageBufferInVertexShaderNoLayout) {
767     wgpu::ShaderModule vsModuleWithStorageBuffer = utils::CreateShaderModule(device, R"(
768         [[block]] struct Dst {
769             data : array<u32, 100>;
770         };
771         [[group(0), binding(0)]] var<storage, read_write> dst : Dst;
772         [[stage(vertex)]] fn main([[builtin(vertex_index)]] VertexIndex : u32) -> [[builtin(position)]] vec4<f32> {
773             dst.data[VertexIndex] = 0x1234u;
774             return vec4<f32>();
775         })");
776 
777     utils::ComboRenderPipelineDescriptor descriptor;
778     descriptor.layout = nullptr;
779     descriptor.vertex.module = vsModuleWithStorageBuffer;
780     descriptor.cFragment.module = fsModule;
781     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
782 }
783 
784 // Tests that only strip primitive topologies allow an index format
TEST_F(RenderPipelineValidationTest,StripIndexFormatAllowed)785 TEST_F(RenderPipelineValidationTest, StripIndexFormatAllowed) {
786     constexpr uint32_t kNumStripType = 2u;
787     constexpr uint32_t kNumListType = 3u;
788     constexpr uint32_t kNumIndexFormat = 3u;
789 
790     std::array<wgpu::PrimitiveTopology, kNumStripType> kStripTopologyTypes = {
791         {wgpu::PrimitiveTopology::LineStrip, wgpu::PrimitiveTopology::TriangleStrip}};
792 
793     std::array<wgpu::PrimitiveTopology, kNumListType> kListTopologyTypes = {
794         {wgpu::PrimitiveTopology::PointList, wgpu::PrimitiveTopology::LineList,
795          wgpu::PrimitiveTopology::TriangleList}};
796 
797     std::array<wgpu::IndexFormat, kNumIndexFormat> kIndexFormatTypes = {
798         {wgpu::IndexFormat::Undefined, wgpu::IndexFormat::Uint16, wgpu::IndexFormat::Uint32}};
799 
800     for (wgpu::PrimitiveTopology primitiveTopology : kStripTopologyTypes) {
801         for (wgpu::IndexFormat indexFormat : kIndexFormatTypes) {
802             utils::ComboRenderPipelineDescriptor descriptor;
803             descriptor.vertex.module = vsModule;
804             descriptor.cFragment.module = fsModule;
805             descriptor.primitive.topology = primitiveTopology;
806             descriptor.primitive.stripIndexFormat = indexFormat;
807 
808             // Always succeeds, regardless of if an index format is given.
809             device.CreateRenderPipeline(&descriptor);
810         }
811     }
812 
813     for (wgpu::PrimitiveTopology primitiveTopology : kListTopologyTypes) {
814         for (wgpu::IndexFormat indexFormat : kIndexFormatTypes) {
815             utils::ComboRenderPipelineDescriptor descriptor;
816             descriptor.vertex.module = vsModule;
817             descriptor.cFragment.module = fsModule;
818             descriptor.primitive.topology = primitiveTopology;
819             descriptor.primitive.stripIndexFormat = indexFormat;
820 
821             if (indexFormat == wgpu::IndexFormat::Undefined) {
822                 // Succeeds even when the index format is undefined because the
823                 // primitive topology isn't a strip type.
824                 device.CreateRenderPipeline(&descriptor);
825             } else {
826                 ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
827             }
828         }
829     }
830 }
831 
832 // Test that specifying a clampDepth value results in an error if the feature is not enabled.
TEST_F(RenderPipelineValidationTest,ClampDepthWithoutFeature)833 TEST_F(RenderPipelineValidationTest, ClampDepthWithoutFeature) {
834     {
835         utils::ComboRenderPipelineDescriptor descriptor;
836         descriptor.vertex.module = vsModule;
837         descriptor.cFragment.module = fsModule;
838         wgpu::PrimitiveDepthClampingState clampingState;
839         clampingState.clampDepth = true;
840         descriptor.primitive.nextInChain = &clampingState;
841         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
842     }
843     {
844         utils::ComboRenderPipelineDescriptor descriptor;
845         descriptor.vertex.module = vsModule;
846         descriptor.cFragment.module = fsModule;
847         wgpu::PrimitiveDepthClampingState clampingState;
848         clampingState.clampDepth = false;
849         descriptor.primitive.nextInChain = &clampingState;
850         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
851     }
852 }
853 
854 // Test that depthStencil.depthCompare must not be undefiend.
TEST_F(RenderPipelineValidationTest,DepthCompareUndefinedIsError)855 TEST_F(RenderPipelineValidationTest, DepthCompareUndefinedIsError) {
856     utils::ComboRenderPipelineDescriptor descriptor;
857     descriptor.vertex.module = vsModule;
858     descriptor.cFragment.module = fsModule;
859     descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth32Float);
860 
861     // Control case: Always is valid.
862     descriptor.cDepthStencil.depthCompare = wgpu::CompareFunction::Always;
863     device.CreateRenderPipeline(&descriptor);
864 
865     // Error case: Undefined is invalid.
866     descriptor.cDepthStencil.depthCompare = wgpu::CompareFunction::Undefined;
867     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
868 }
869 
870 // Test that the entryPoint names must be present for the correct stage in the shader module.
TEST_F(RenderPipelineValidationTest,EntryPointNameValidation)871 TEST_F(RenderPipelineValidationTest, EntryPointNameValidation) {
872     wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
873         [[stage(vertex)]] fn vertex_main() -> [[builtin(position)]] vec4<f32> {
874             return vec4<f32>(0.0, 0.0, 0.0, 1.0);
875         }
876 
877         [[stage(fragment)]] fn fragment_main() -> [[location(0)]] vec4<f32> {
878             return vec4<f32>(1.0, 0.0, 0.0, 1.0);
879         }
880     )");
881 
882     utils::ComboRenderPipelineDescriptor descriptor;
883     descriptor.vertex.module = module;
884     descriptor.vertex.entryPoint = "vertex_main";
885     descriptor.cFragment.module = module;
886     descriptor.cFragment.entryPoint = "fragment_main";
887 
888     // Success case.
889     device.CreateRenderPipeline(&descriptor);
890 
891     // Test for the vertex stage entryPoint name.
892     {
893         // The entryPoint name doesn't exist in the module.
894         descriptor.vertex.entryPoint = "main";
895         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
896 
897         // The entryPoint name exists, but not for the correct stage.
898         descriptor.vertex.entryPoint = "fragment_main";
899         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
900     }
901 
902     descriptor.vertex.entryPoint = "vertex_main";
903 
904     // Test for the fragment stage entryPoint name.
905     {
906         // The entryPoint name doesn't exist in the module.
907         descriptor.cFragment.entryPoint = "main";
908         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
909 
910         // The entryPoint name exists, but not for the correct stage.
911         descriptor.cFragment.entryPoint = "vertex_main";
912         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
913     }
914 }
915 
916 // Test that vertex attrib validation is for the correct entryPoint
TEST_F(RenderPipelineValidationTest,VertexAttribCorrectEntryPoint)917 TEST_F(RenderPipelineValidationTest, VertexAttribCorrectEntryPoint) {
918     wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
919         [[stage(vertex)]] fn vertex0([[location(0)]] attrib0 : vec4<f32>)
920                                     -> [[builtin(position)]] vec4<f32> {
921             return attrib0;
922         }
923         [[stage(vertex)]] fn vertex1([[location(1)]] attrib1 : vec4<f32>)
924                                     -> [[builtin(position)]] vec4<f32> {
925             return attrib1;
926         }
927     )");
928 
929     utils::ComboRenderPipelineDescriptor descriptor;
930     descriptor.vertex.module = module;
931     descriptor.cFragment.module = fsModule;
932 
933     descriptor.vertex.bufferCount = 1;
934     descriptor.cBuffers[0].attributeCount = 1;
935     descriptor.cBuffers[0].arrayStride = 16;
936     descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x4;
937     descriptor.cAttributes[0].offset = 0;
938 
939     // Success cases, the attribute used by the entryPoint is declared in the pipeline.
940     descriptor.vertex.entryPoint = "vertex0";
941     descriptor.cAttributes[0].shaderLocation = 0;
942     device.CreateRenderPipeline(&descriptor);
943 
944     descriptor.vertex.entryPoint = "vertex1";
945     descriptor.cAttributes[0].shaderLocation = 1;
946     device.CreateRenderPipeline(&descriptor);
947 
948     // Error cases, the attribute used by the entryPoint isn't declared in the pipeline.
949     descriptor.vertex.entryPoint = "vertex1";
950     descriptor.cAttributes[0].shaderLocation = 0;
951     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
952 
953     descriptor.vertex.entryPoint = "vertex0";
954     descriptor.cAttributes[0].shaderLocation = 1;
955     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
956 }
957 
958 // Test that fragment output validation is for the correct entryPoint
TEST_F(RenderPipelineValidationTest,FragmentOutputCorrectEntryPoint)959 TEST_F(RenderPipelineValidationTest, FragmentOutputCorrectEntryPoint) {
960     wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
961         [[stage(fragment)]] fn fragmentFloat() -> [[location(0)]] vec4<f32> {
962             return vec4<f32>(0.0, 0.0, 0.0, 0.0);
963         }
964         [[stage(fragment)]] fn fragmentUint() -> [[location(0)]] vec4<u32> {
965             return vec4<u32>(0u, 0u, 0u, 0u);
966         }
967     )");
968 
969     utils::ComboRenderPipelineDescriptor descriptor;
970     descriptor.vertex.module = vsModule;
971     descriptor.cFragment.module = module;
972 
973     // Success case, the component type matches between the pipeline and the entryPoint
974     descriptor.cFragment.entryPoint = "fragmentFloat";
975     descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA32Float;
976     device.CreateRenderPipeline(&descriptor);
977 
978     descriptor.cFragment.entryPoint = "fragmentUint";
979     descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA32Uint;
980     device.CreateRenderPipeline(&descriptor);
981 
982     // Error case, the component type doesn't match between the pipeline and the entryPoint
983     descriptor.cFragment.entryPoint = "fragmentUint";
984     descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA32Float;
985     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
986 
987     descriptor.cFragment.entryPoint = "fragmentFloat";
988     descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA32Uint;
989     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
990 }
991 
992 // Test that unwritten fragment outputs must have a write mask of 0.
TEST_F(RenderPipelineValidationTest,UnwrittenFragmentOutputsMask0)993 TEST_F(RenderPipelineValidationTest, UnwrittenFragmentOutputsMask0) {
994     wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
995         [[stage(vertex)]] fn main() -> [[builtin(position)]] vec4<f32> {
996             return vec4<f32>();
997         }
998     )");
999 
1000     wgpu::ShaderModule fsModuleWriteNone = utils::CreateShaderModule(device, R"(
1001         [[stage(fragment)]] fn main() {}
1002     )");
1003 
1004     wgpu::ShaderModule fsModuleWrite0 = utils::CreateShaderModule(device, R"(
1005         [[stage(fragment)]] fn main() -> [[location(0)]] vec4<f32> {
1006             return vec4<f32>();
1007         }
1008     )");
1009 
1010     wgpu::ShaderModule fsModuleWrite1 = utils::CreateShaderModule(device, R"(
1011         [[stage(fragment)]] fn main() -> [[location(1)]] vec4<f32> {
1012             return vec4<f32>();
1013         }
1014     )");
1015 
1016     wgpu::ShaderModule fsModuleWriteBoth = utils::CreateShaderModule(device, R"(
1017         struct FragmentOut {
1018             [[location(0)]] target0 : vec4<f32>;
1019             [[location(1)]] target1 : vec4<f32>;
1020         };
1021         [[stage(fragment)]] fn main() -> FragmentOut {
1022             var out : FragmentOut;
1023             return out;
1024         }
1025     )");
1026 
1027     // Control case: write to target 0
1028     {
1029         utils::ComboRenderPipelineDescriptor descriptor;
1030         descriptor.vertex.module = vsModule;
1031 
1032         descriptor.cFragment.targetCount = 1;
1033         descriptor.cFragment.module = fsModuleWrite0;
1034         device.CreateRenderPipeline(&descriptor);
1035     }
1036 
1037     // Control case: write to target 0 and target 1
1038     {
1039         utils::ComboRenderPipelineDescriptor descriptor;
1040         descriptor.vertex.module = vsModule;
1041 
1042         descriptor.cFragment.targetCount = 2;
1043         descriptor.cFragment.module = fsModuleWriteBoth;
1044         device.CreateRenderPipeline(&descriptor);
1045     }
1046 
1047     // Write only target 1 (not in pipeline fragment state).
1048     // Errors because target 0 does not have a write mask of 0.
1049     {
1050         utils::ComboRenderPipelineDescriptor descriptor;
1051         descriptor.vertex.module = vsModule;
1052 
1053         descriptor.cFragment.targetCount = 1;
1054         descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::All;
1055         descriptor.cFragment.module = fsModuleWrite1;
1056         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
1057     }
1058 
1059     // Write only target 1 (not in pipeline fragment state).
1060     // OK because target 0 has a write mask of 0.
1061     {
1062         utils::ComboRenderPipelineDescriptor descriptor;
1063         descriptor.vertex.module = vsModule;
1064 
1065         descriptor.cFragment.targetCount = 1;
1066         descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
1067         descriptor.cFragment.module = fsModuleWrite1;
1068         device.CreateRenderPipeline(&descriptor);
1069     }
1070 
1071     // Write only target 0 with two color targets.
1072     // Errors because target 1 does not have a write mask of 0.
1073     {
1074         utils::ComboRenderPipelineDescriptor descriptor;
1075         descriptor.vertex.module = vsModule;
1076 
1077         descriptor.cFragment.targetCount = 2;
1078         descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::Red;
1079         descriptor.cTargets[1].writeMask = wgpu::ColorWriteMask::Alpha;
1080         descriptor.cFragment.module = fsModuleWrite0;
1081         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
1082     }
1083 
1084     // Write only target 0 with two color targets.
1085     // OK because target 1 has a write mask of 0.
1086     {
1087         utils::ComboRenderPipelineDescriptor descriptor;
1088         descriptor.vertex.module = vsModule;
1089 
1090         descriptor.cFragment.targetCount = 2;
1091         descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::All;
1092         descriptor.cTargets[1].writeMask = wgpu::ColorWriteMask::None;
1093         descriptor.cFragment.module = fsModuleWrite0;
1094         device.CreateRenderPipeline(&descriptor);
1095     }
1096 
1097     // Write nothing with two color targets.
1098     // Errors because both target 0 and 1 have nonzero write masks.
1099     {
1100         utils::ComboRenderPipelineDescriptor descriptor;
1101         descriptor.vertex.module = vsModule;
1102 
1103         descriptor.cFragment.targetCount = 2;
1104         descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::Red;
1105         descriptor.cTargets[1].writeMask = wgpu::ColorWriteMask::Green;
1106         descriptor.cFragment.module = fsModuleWriteNone;
1107         ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
1108     }
1109 
1110     // Write nothing with two color targets.
1111     // OK because target 0 and 1 have write masks of 0.
1112     {
1113         utils::ComboRenderPipelineDescriptor descriptor;
1114         descriptor.vertex.module = vsModule;
1115 
1116         descriptor.cFragment.targetCount = 2;
1117         descriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::None;
1118         descriptor.cTargets[1].writeMask = wgpu::ColorWriteMask::None;
1119         descriptor.cFragment.module = fsModuleWriteNone;
1120         device.CreateRenderPipeline(&descriptor);
1121     }
1122 }
1123 
1124 // Test that fragment output validation is for the correct entryPoint
TEST_F(RenderPipelineValidationTest,BindingsFromCorrectEntryPoint)1125 TEST_F(RenderPipelineValidationTest, BindingsFromCorrectEntryPoint) {
1126     wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
1127         [[block]] struct Uniforms {
1128             data : vec4<f32>;
1129         };
1130         [[group(0), binding(0)]] var<uniform> var0 : Uniforms;
1131         [[group(0), binding(1)]] var<uniform> var1 : Uniforms;
1132 
1133         [[stage(vertex)]] fn vertex0() -> [[builtin(position)]] vec4<f32> {
1134             return var0.data;
1135         }
1136         [[stage(vertex)]] fn vertex1() -> [[builtin(position)]] vec4<f32> {
1137             return var1.data;
1138         }
1139     )");
1140 
1141     wgpu::BindGroupLayout bgl0 = utils::MakeBindGroupLayout(
1142         device, {{0, wgpu::ShaderStage::Vertex, wgpu::BufferBindingType::Uniform}});
1143     wgpu::PipelineLayout layout0 = utils::MakeBasicPipelineLayout(device, &bgl0);
1144 
1145     wgpu::BindGroupLayout bgl1 = utils::MakeBindGroupLayout(
1146         device, {{1, wgpu::ShaderStage::Vertex, wgpu::BufferBindingType::Uniform}});
1147     wgpu::PipelineLayout layout1 = utils::MakeBasicPipelineLayout(device, &bgl1);
1148 
1149     utils::ComboRenderPipelineDescriptor descriptor;
1150     descriptor.vertex.module = module;
1151     descriptor.cFragment.module = fsModule;
1152 
1153     // Success case, the BGL matches the bindings used by the entryPoint
1154     descriptor.vertex.entryPoint = "vertex0";
1155     descriptor.layout = layout0;
1156     device.CreateRenderPipeline(&descriptor);
1157 
1158     descriptor.vertex.entryPoint = "vertex1";
1159     descriptor.layout = layout1;
1160     device.CreateRenderPipeline(&descriptor);
1161 
1162     // Error case, the BGL doesn't match the bindings used by the entryPoint
1163     descriptor.vertex.entryPoint = "vertex1";
1164     descriptor.layout = layout0;
1165     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
1166 
1167     descriptor.vertex.entryPoint = "vertex0";
1168     descriptor.layout = layout1;
1169     ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
1170 }
1171 
1172 class DepthClampingValidationTest : public RenderPipelineValidationTest {
1173   protected:
CreateTestDevice()1174     WGPUDevice CreateTestDevice() override {
1175         dawn_native::DawnDeviceDescriptor descriptor;
1176         descriptor.requiredFeatures = {"depth-clamping"};
1177         return adapter.CreateDevice(&descriptor);
1178     }
1179 };
1180 
1181 // Tests that specifying a clampDepth value succeeds if the feature is enabled.
TEST_F(DepthClampingValidationTest,Success)1182 TEST_F(DepthClampingValidationTest, Success) {
1183     {
1184         utils::ComboRenderPipelineDescriptor descriptor;
1185         descriptor.vertex.module = vsModule;
1186         descriptor.cFragment.module = fsModule;
1187         wgpu::PrimitiveDepthClampingState clampingState;
1188         clampingState.clampDepth = true;
1189         descriptor.primitive.nextInChain = &clampingState;
1190         device.CreateRenderPipeline(&descriptor);
1191     }
1192     {
1193         utils::ComboRenderPipelineDescriptor descriptor;
1194         descriptor.vertex.module = vsModule;
1195         descriptor.cFragment.module = fsModule;
1196         wgpu::PrimitiveDepthClampingState clampingState;
1197         clampingState.clampDepth = false;
1198         descriptor.primitive.nextInChain = &clampingState;
1199         device.CreateRenderPipeline(&descriptor);
1200     }
1201 }
1202 
1203 class InterStageVariableMatchingValidationTest : public RenderPipelineValidationTest {
1204   protected:
CheckCreatingRenderPipeline(wgpu::ShaderModule vertexModule,wgpu::ShaderModule fragmentModule,bool shouldSucceed)1205     void CheckCreatingRenderPipeline(wgpu::ShaderModule vertexModule,
1206                                      wgpu::ShaderModule fragmentModule,
1207                                      bool shouldSucceed) {
1208         utils::ComboRenderPipelineDescriptor descriptor;
1209         descriptor.vertex.module = vertexModule;
1210         descriptor.cFragment.module = fragmentModule;
1211         if (shouldSucceed) {
1212             device.CreateRenderPipeline(&descriptor);
1213         } else {
1214             ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
1215         }
1216     }
1217 };
1218 
1219 // Tests that creating render pipeline should fail when there is a vertex output that doesn't have
1220 // its corresponding fragment input at the same location, and there is a fragment input that
1221 // doesn't have its corresponding vertex output at the same location.
TEST_F(InterStageVariableMatchingValidationTest,MissingDeclarationAtSameLocation)1222 TEST_F(InterStageVariableMatchingValidationTest, MissingDeclarationAtSameLocation) {
1223     wgpu::ShaderModule vertexModuleOutputAtLocation0 = utils::CreateShaderModule(device, R"(
1224             struct A {
1225                 [[location(0)]] vout: f32;
1226                 [[builtin(position)]] pos: vec4<f32>;
1227             };
1228             [[stage(vertex)]] fn main() -> A {
1229                 var vertexOut: A;
1230                 vertexOut.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
1231                 return vertexOut;
1232             })");
1233     wgpu::ShaderModule fragmentModuleAtLocation0 = utils::CreateShaderModule(device, R"(
1234             struct B {
1235                 [[location(0)]] fin: f32;
1236             };
1237             [[stage(fragment)]] fn main(fragmentIn: B) -> [[location(0)]] vec4<f32>  {
1238                 return vec4<f32>(fragmentIn.fin, 0.0, 0.0, 1.0);
1239             })");
1240     wgpu::ShaderModule fragmentModuleInputAtLocation1 = utils::CreateShaderModule(device, R"(
1241             struct A {
1242                 [[location(1)]] vout: f32;
1243             };
1244             [[stage(fragment)]] fn main(vertexOut: A) -> [[location(0)]] vec4<f32>  {
1245                 return vec4<f32>(vertexOut.vout, 0.0, 0.0, 1.0);
1246             })");
1247     wgpu::ShaderModule vertexModuleOutputAtLocation1 = utils::CreateShaderModule(device, R"(
1248             struct B {
1249                 [[location(1)]] fin: f32;
1250                 [[builtin(position)]] pos: vec4<f32>;
1251             };
1252             [[stage(vertex)]] fn main() -> B {
1253                 var fragmentIn: B;
1254                 fragmentIn.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
1255                 return fragmentIn;
1256             })");
1257 
1258     {
1259         CheckCreatingRenderPipeline(vertexModuleOutputAtLocation0, fsModule, false);
1260         CheckCreatingRenderPipeline(vsModule, fragmentModuleAtLocation0, false);
1261         CheckCreatingRenderPipeline(vertexModuleOutputAtLocation0, fragmentModuleInputAtLocation1,
1262                                     false);
1263         CheckCreatingRenderPipeline(vertexModuleOutputAtLocation1, fragmentModuleAtLocation0,
1264                                     false);
1265     }
1266 
1267     {
1268         CheckCreatingRenderPipeline(vertexModuleOutputAtLocation0, fragmentModuleAtLocation0, true);
1269         CheckCreatingRenderPipeline(vertexModuleOutputAtLocation1, fragmentModuleInputAtLocation1,
1270                                     true);
1271     }
1272 }
1273 
1274 // Tests that creating render pipeline should fail when the type of a vertex stage output variable
1275 // doesn't match the type of the fragment stage input variable at the same location.
TEST_F(InterStageVariableMatchingValidationTest,DifferentTypeAtSameLocation)1276 TEST_F(InterStageVariableMatchingValidationTest, DifferentTypeAtSameLocation) {
1277     constexpr std::array<const char*, 12> kTypes = {{"f32", "vec2<f32>", "vec3<f32>", "vec4<f32>",
1278                                                      "i32", "vec2<i32>", "vec3<i32>", "vec4<i32>",
1279                                                      "u32", "vec2<u32>", "vec3<u32>", "vec4<u32>"}};
1280 
1281     std::array<wgpu::ShaderModule, 12> vertexModules;
1282     std::array<wgpu::ShaderModule, 12> fragmentModules;
1283     for (uint32_t i = 0; i < kTypes.size(); ++i) {
1284         std::string interfaceDeclaration;
1285         {
1286             std::ostringstream sstream;
1287             sstream << "struct A { [[location(0)]] a: " << kTypes[i] << ";" << std::endl;
1288             interfaceDeclaration = sstream.str();
1289         }
1290         {
1291             std::ostringstream vertexStream;
1292             vertexStream << interfaceDeclaration << R"(
1293                     [[builtin(position)]] pos: vec4<f32>;
1294                 };
1295                 [[stage(vertex)]] fn main() -> A {
1296                     var vertexOut: A;
1297                     vertexOut.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
1298                     return vertexOut;
1299                 })";
1300             vertexModules[i] = utils::CreateShaderModule(device, vertexStream.str().c_str());
1301         }
1302         {
1303             std::ostringstream fragmentStream;
1304             fragmentStream << interfaceDeclaration << R"(
1305                 };
1306                 [[stage(fragment)]] fn main(fragmentIn: A) -> [[location(0)]] vec4<f32> {
1307                     return vec4<f32>(0.0, 0.0, 0.0, 1.0);
1308                 })";
1309             fragmentModules[i] = utils::CreateShaderModule(device, fragmentStream.str().c_str());
1310         }
1311     }
1312 
1313     for (uint32_t vertexModuleIndex = 0; vertexModuleIndex < kTypes.size(); ++vertexModuleIndex) {
1314         wgpu::ShaderModule vertexModule = vertexModules[vertexModuleIndex];
1315         for (uint32_t fragmentModuleIndex = 0; fragmentModuleIndex < kTypes.size();
1316              ++fragmentModuleIndex) {
1317             wgpu::ShaderModule fragmentModule = fragmentModules[fragmentModuleIndex];
1318             bool shouldSuccess = vertexModuleIndex == fragmentModuleIndex;
1319             CheckCreatingRenderPipeline(vertexModule, fragmentModule, shouldSuccess);
1320         }
1321     }
1322 }
1323 
1324 // Tests that creating render pipeline should fail when the interpolation attribute of a vertex
1325 // stage output variable doesn't match the type of the fragment stage input variable at the same
1326 // location.
TEST_F(InterStageVariableMatchingValidationTest,DifferentInterpolationAttributeAtSameLocation)1327 TEST_F(InterStageVariableMatchingValidationTest, DifferentInterpolationAttributeAtSameLocation) {
1328     enum class InterpolationType : uint8_t {
1329         None = 0,
1330         Perspective,
1331         Linear,
1332         Flat,
1333         Count,
1334     };
1335     enum class InterpolationSampling : uint8_t {
1336         None = 0,
1337         Center,
1338         Centroid,
1339         Sample,
1340         Count,
1341     };
1342     constexpr std::array<const char*, static_cast<size_t>(InterpolationType::Count)>
1343         kInterpolationTypeString = {{"", "perspective", "linear", "flat"}};
1344     constexpr std::array<const char*, static_cast<size_t>(InterpolationSampling::Count)>
1345         kInterpolationSamplingString = {{"", "center", "centroid", "sample"}};
1346 
1347     struct InterpolationAttribute {
1348         InterpolationType interpolationType;
1349         InterpolationSampling interpolationSampling;
1350     };
1351 
1352     // Interpolation sampling is not used with flat interpolation.
1353     constexpr std::array<InterpolationAttribute, 10> validInterpolationAttributes = {{
1354         {InterpolationType::None, InterpolationSampling::None},
1355         {InterpolationType::Flat, InterpolationSampling::None},
1356         {InterpolationType::Linear, InterpolationSampling::None},
1357         {InterpolationType::Linear, InterpolationSampling::Center},
1358         {InterpolationType::Linear, InterpolationSampling::Centroid},
1359         {InterpolationType::Linear, InterpolationSampling::Sample},
1360         {InterpolationType::Perspective, InterpolationSampling::None},
1361         {InterpolationType::Perspective, InterpolationSampling::Center},
1362         {InterpolationType::Perspective, InterpolationSampling::Centroid},
1363         {InterpolationType::Perspective, InterpolationSampling::Sample},
1364     }};
1365 
1366     std::vector<wgpu::ShaderModule> vertexModules(validInterpolationAttributes.size());
1367     std::vector<wgpu::ShaderModule> fragmentModules(validInterpolationAttributes.size());
1368     for (uint32_t i = 0; i < validInterpolationAttributes.size(); ++i) {
1369         std::string interfaceDeclaration;
1370         {
1371             const auto& interpolationAttribute = validInterpolationAttributes[i];
1372             std::ostringstream sstream;
1373             sstream << "struct A { [[location(0)";
1374             if (interpolationAttribute.interpolationType != InterpolationType::None) {
1375                 sstream << ", interpolate("
1376                         << kInterpolationTypeString[static_cast<uint8_t>(
1377                                interpolationAttribute.interpolationType)];
1378                 if (interpolationAttribute.interpolationSampling != InterpolationSampling::None) {
1379                     sstream << ", "
1380                             << kInterpolationSamplingString[static_cast<uint8_t>(
1381                                    interpolationAttribute.interpolationSampling)];
1382                 }
1383                 sstream << ")";
1384             }
1385             sstream << " ]] a : vec4<f32>;" << std::endl;
1386             interfaceDeclaration = sstream.str();
1387         }
1388         {
1389             std::ostringstream vertexStream;
1390             vertexStream << interfaceDeclaration << R"(
1391                     [[builtin(position)]] pos: vec4<f32>;
1392                 };
1393                 [[stage(vertex)]] fn main() -> A {
1394                     var vertexOut: A;
1395                     vertexOut.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
1396                     return vertexOut;
1397                 })";
1398             vertexModules[i] = utils::CreateShaderModule(device, vertexStream.str().c_str());
1399         }
1400         {
1401             std::ostringstream fragmentStream;
1402             fragmentStream << interfaceDeclaration << R"(
1403                 };
1404                 [[stage(fragment)]] fn main(fragmentIn: A) -> [[location(0)]] vec4<f32> {
1405                     return fragmentIn.a;
1406                 })";
1407             fragmentModules[i] = utils::CreateShaderModule(device, fragmentStream.str().c_str());
1408         }
1409     }
1410 
1411     auto GetAppliedInterpolationAttribute = [](const InterpolationAttribute& attribute) {
1412         InterpolationAttribute appliedAttribute = {attribute.interpolationType,
1413                                                    attribute.interpolationSampling};
1414         switch (attribute.interpolationType) {
1415             // If the interpolation attribute is not specified, then
1416             // [[interpolate(perspective, center)]] or [[interpolate(perspective)]] is assumed.
1417             case InterpolationType::None:
1418                 appliedAttribute.interpolationType = InterpolationType::Perspective;
1419                 appliedAttribute.interpolationSampling = InterpolationSampling::Center;
1420                 break;
1421 
1422             // If the interpolation type is perspective or linear, and the interpolation
1423             // sampling is not specified, then 'center' is assumed.
1424             case InterpolationType::Perspective:
1425             case InterpolationType::Linear:
1426                 if (appliedAttribute.interpolationSampling == InterpolationSampling::None) {
1427                     appliedAttribute.interpolationSampling = InterpolationSampling::Center;
1428                 }
1429                 break;
1430 
1431             case InterpolationType::Flat:
1432                 break;
1433             default:
1434                 UNREACHABLE();
1435         }
1436         return appliedAttribute;
1437     };
1438 
1439     auto InterpolationAttributeMatch = [GetAppliedInterpolationAttribute](
1440                                            const InterpolationAttribute& attribute1,
1441                                            const InterpolationAttribute& attribute2) {
1442         InterpolationAttribute appliedAttribute1 = GetAppliedInterpolationAttribute(attribute1);
1443         InterpolationAttribute appliedAttribute2 = GetAppliedInterpolationAttribute(attribute2);
1444 
1445         return appliedAttribute1.interpolationType == appliedAttribute2.interpolationType &&
1446                appliedAttribute1.interpolationSampling == appliedAttribute2.interpolationSampling;
1447     };
1448 
1449     for (uint32_t vertexModuleIndex = 0; vertexModuleIndex < validInterpolationAttributes.size();
1450          ++vertexModuleIndex) {
1451         wgpu::ShaderModule vertexModule = vertexModules[vertexModuleIndex];
1452         for (uint32_t fragmentModuleIndex = 0;
1453              fragmentModuleIndex < validInterpolationAttributes.size(); ++fragmentModuleIndex) {
1454             wgpu::ShaderModule fragmentModule = fragmentModules[fragmentModuleIndex];
1455             bool shouldSuccess =
1456                 InterpolationAttributeMatch(validInterpolationAttributes[vertexModuleIndex],
1457                                             validInterpolationAttributes[fragmentModuleIndex]);
1458             CheckCreatingRenderPipeline(vertexModule, fragmentModule, shouldSuccess);
1459         }
1460     }
1461 }
1462