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