dawn-cmake/src/dawn/tests/unittests/validation/ShaderModuleValidationTests.cpp
Jiawei Shao ab00dd0725 Remove ShaderModuleValidationTest.FragmentOutputLocationExceedsMaxColorAttachments
This patch removes the test
ShaderModuleValidationTest.FragmentOutputLocationExceedsMaxColorAttachments
from dawn_unittests because now the emission of the validation errors about
the maximum value of fragment output location is delayed from
CreateShaderModule() to CreateRenderPipeline(), however this test expects the
error will still be reported in CreateShaderModule().

Previously this test can still pass because kMaxColorAttachments is declared as
an uint8_t, so it will be added as "\b" (ascii code is 8) in string stream when
constructing the shader, then we can still get a shader-creation error because
it is not an integer that is inside the bracket of the location() expression.

Bug: dawn:986
Change-Id: Iaf03f83b2c27e3d9e986f813b2086726b51a0aeb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/116554
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
2023-01-10 01:09:28 +00:00

603 lines
25 KiB
C++

// Copyright 2018 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <sstream>
#include <string>
#include "dawn/common/Constants.h"
#include "dawn/native/ShaderModule.h"
#include "dawn/tests/unittests/validation/ValidationTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
class ShaderModuleValidationTest : public ValidationTest {};
// Test case with a simpler shader that should successfully be created
TEST_F(ShaderModuleValidationTest, CreationSuccess) {
const char* shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %fragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
OpSourceExtension "GL_GOOGLE_include_directive"
OpName %main "main"
OpName %fragColor "fragColor"
OpDecorate %fragColor Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%fragColor = OpVariable %_ptr_Output_v4float Output
%float_1 = OpConstant %float 1
%float_0 = OpConstant %float 0
%12 = OpConstantComposite %v4float %float_1 %float_0 %float_0 %float_1
%main = OpFunction %void None %3
%5 = OpLabel
OpStore %fragColor %12
OpReturn
OpFunctionEnd)";
utils::CreateShaderModuleFromASM(device, shader);
}
// Test that it is invalid to create a shader module with no chained descriptor. (It must be
// WGSL or SPIRV, not empty)
TEST_F(ShaderModuleValidationTest, NoChainedDescriptor) {
wgpu::ShaderModuleDescriptor desc = {};
ASSERT_DEVICE_ERROR(device.CreateShaderModule(&desc));
}
// Test that it is not allowed to use combined texture and sampler.
TEST_F(ShaderModuleValidationTest, CombinedTextureAndSampler) {
// SPIR-V ASM produced by glslang for the following fragment shader:
//
// #version 450
// layout(set = 0, binding = 0) uniform sampler2D tex;
// void main () {}
//
// Note that the following defines an interface combined texture/sampler which is not allowed
// in Dawn / WebGPU.
//
// %8 = OpTypeSampledImage %7
// %_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
// %tex = OpVariable %_ptr_UniformConstant_8 UniformConstant
const char* shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %tex "tex"
OpDecorate %tex DescriptorSet 0
OpDecorate %tex Binding 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%7 = OpTypeImage %float 2D 0 0 0 1 Unknown
%8 = OpTypeSampledImage %7
%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8
%tex = OpVariable %_ptr_UniformConstant_8 UniformConstant
%main = OpFunction %void None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_DEVICE_ERROR(utils::CreateShaderModuleFromASM(device, shader));
}
// Test that it is not allowed to declare a multisampled-array interface texture.
// TODO(enga): Also test multisampled cube, cube array, and 3D. These have no GLSL keywords.
TEST_F(ShaderModuleValidationTest, MultisampledArrayTexture) {
// SPIR-V ASM produced by glslang for the following fragment shader:
//
// #version 450
// layout(set=0, binding=0) uniform texture2DMSArray tex;
// void main () {}}
//
// Note that the following defines an interface array multisampled texture which is not allowed
// in Dawn / WebGPU.
//
// %7 = OpTypeImage %float 2D 0 1 1 1 Unknown
// %_ptr_UniformConstant_7 = OpTypePointer UniformConstant %7
// %tex = OpVariable %_ptr_UniformConstant_7 UniformConstant
const char* shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpName %main "main"
OpName %tex "tex"
OpDecorate %tex DescriptorSet 0
OpDecorate %tex Binding 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%7 = OpTypeImage %float 2D 0 1 1 1 Unknown
%_ptr_UniformConstant_7 = OpTypePointer UniformConstant %7
%tex = OpVariable %_ptr_UniformConstant_7 UniformConstant
%main = OpFunction %void None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
ASSERT_DEVICE_ERROR(utils::CreateShaderModuleFromASM(device, shader));
}
// Tests that shader module compilation messages can be queried.
TEST_F(ShaderModuleValidationTest, GetCompilationMessages) {
// This test works assuming ShaderModule is backed by a dawn::native::ShaderModuleBase, which
// is not the case on the wire.
DAWN_SKIP_TEST_IF(UsesWire());
wgpu::ShaderModule shaderModule = utils::CreateShaderModule(device, R"(
@fragment fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
})");
dawn::native::ShaderModuleBase* shaderModuleBase = dawn::native::FromAPI(shaderModule.Get());
dawn::native::OwnedCompilationMessages* messages = shaderModuleBase->GetCompilationMessages();
messages->ClearMessages();
messages->AddMessageForTesting("Info Message");
messages->AddMessageForTesting("Warning Message", wgpu::CompilationMessageType::Warning);
messages->AddMessageForTesting("Error Message", wgpu::CompilationMessageType::Error, 3, 4);
messages->AddMessageForTesting("Complete Message", wgpu::CompilationMessageType::Info, 3, 4, 5,
6);
auto callback = [](WGPUCompilationInfoRequestStatus status, const WGPUCompilationInfo* info,
void* userdata) {
ASSERT_EQ(WGPUCompilationInfoRequestStatus_Success, status);
ASSERT_NE(nullptr, info);
ASSERT_EQ(4u, info->messageCount);
const WGPUCompilationMessage* message = &info->messages[0];
ASSERT_STREQ("Info Message", message->message);
ASSERT_EQ(WGPUCompilationMessageType_Info, message->type);
ASSERT_EQ(0u, message->lineNum);
ASSERT_EQ(0u, message->linePos);
message = &info->messages[1];
ASSERT_STREQ("Warning Message", message->message);
ASSERT_EQ(WGPUCompilationMessageType_Warning, message->type);
ASSERT_EQ(0u, message->lineNum);
ASSERT_EQ(0u, message->linePos);
message = &info->messages[2];
ASSERT_STREQ("Error Message", message->message);
ASSERT_EQ(WGPUCompilationMessageType_Error, message->type);
ASSERT_EQ(3u, message->lineNum);
ASSERT_EQ(4u, message->linePos);
message = &info->messages[3];
ASSERT_STREQ("Complete Message", message->message);
ASSERT_EQ(WGPUCompilationMessageType_Info, message->type);
ASSERT_EQ(3u, message->lineNum);
ASSERT_EQ(4u, message->linePos);
ASSERT_EQ(5u, message->offset);
ASSERT_EQ(6u, message->length);
};
shaderModule.GetCompilationInfo(callback, nullptr);
}
// Validate the maximum location of effective inter-stage variables cannot be greater than 14
// (kMaxInterStageShaderComponents / 4 - 1).
TEST_F(ShaderModuleValidationTest, MaximumShaderIOLocations) {
auto CheckTestPipeline = [&](bool success, uint32_t maximumOutputLocation,
wgpu::ShaderStage failingShaderStage) {
// Build the ShaderIO struct containing variables up to maximumOutputLocation.
std::ostringstream stream;
stream << "struct ShaderIO {" << std::endl;
for (uint32_t location = 1; location <= maximumOutputLocation; ++location) {
stream << "@location(" << location << ") var" << location << ": f32," << std::endl;
}
if (failingShaderStage == wgpu::ShaderStage::Vertex) {
stream << " @builtin(position) pos: vec4<f32>,";
}
stream << "}\n";
std::string ioStruct = stream.str();
// Build the test pipeline. Note that it's not possible with just ASSERT_DEVICE_ERROR
// whether it is the vertex or fragment shader that fails. So instead we will look for the
// string "failingVertex" or "failingFragment" in the error message.
utils::ComboRenderPipelineDescriptor pDesc;
pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
const char* errorMatcher = nullptr;
switch (failingShaderStage) {
case wgpu::ShaderStage::Vertex: {
errorMatcher = "failingVertex";
pDesc.vertex.entryPoint = "failingVertex";
pDesc.vertex.module = utils::CreateShaderModule(device, (ioStruct + R"(
@vertex fn failingVertex() -> ShaderIO {
var shaderIO : ShaderIO;
shaderIO.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
return shaderIO;
}
)")
.c_str());
pDesc.cFragment.module = utils::CreateShaderModule(device, R"(
@fragment fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.0);
}
)");
break;
}
case wgpu::ShaderStage::Fragment: {
errorMatcher = "failingFragment";
pDesc.cFragment.entryPoint = "failingFragment";
pDesc.cFragment.module = utils::CreateShaderModule(device, (ioStruct + R"(
@fragment fn failingFragment(io : ShaderIO) -> @location(0) vec4<f32> {
return vec4<f32>(0.0);
}
)")
.c_str());
pDesc.vertex.module = utils::CreateShaderModule(device, R"(
@vertex fn main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0);
}
)");
break;
}
default:
UNREACHABLE();
}
if (success) {
if (failingShaderStage == wgpu::ShaderStage::Vertex) {
// It is allowed that fragment inputs are a subset of the vertex output variables.
device.CreateRenderPipeline(&pDesc);
} else {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pDesc),
testing::HasSubstr("The fragment input at location"));
}
} else {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pDesc),
testing::HasSubstr(errorMatcher));
}
};
// It is allowed to create a shader module with the maximum active vertex output location ==
// (kMaxInterStageShaderVariables - 1);
CheckTestPipeline(true, kMaxInterStageShaderVariables - 1, wgpu::ShaderStage::Vertex);
// It isn't allowed to create a shader module with the maximum active vertex output location ==
// kMaxInterStageShaderVariables;
CheckTestPipeline(false, kMaxInterStageShaderVariables, wgpu::ShaderStage::Vertex);
// It is allowed to create a shader module with the maximum active fragment input location ==
// (kMaxInterStageShaderVariables - 1);
CheckTestPipeline(true, kMaxInterStageShaderVariables - 1, wgpu::ShaderStage::Fragment);
// It isn't allowed to create a shader module with the maximum active vertex output location ==
// kMaxInterStageShaderVariables;
CheckTestPipeline(false, kMaxInterStageShaderVariables, wgpu::ShaderStage::Fragment);
}
// Validate the maximum number of total inter-stage user-defined variable component count and
// built-in variables cannot exceed kMaxInterStageShaderComponents.
TEST_F(ShaderModuleValidationTest, MaximumInterStageShaderComponents) {
auto CheckTestPipeline = [&](bool success,
uint32_t totalUserDefinedInterStageShaderComponentCount,
wgpu::ShaderStage failingShaderStage,
const char* extraBuiltInDeclarations = "",
bool usePointListAsPrimitiveType = false) {
// Build the ShaderIO struct containing totalUserDefinedInterStageShaderComponentCount
// components. Components are added in two parts, a bunch of vec4s, then one additional
// variable for the remaining components.
std::ostringstream stream;
stream << "struct ShaderIO {" << std::endl << extraBuiltInDeclarations << std::endl;
uint32_t vec4InputLocations = totalUserDefinedInterStageShaderComponentCount / 4;
for (uint32_t location = 0; location < vec4InputLocations; ++location) {
stream << "@location(" << location << ") var" << location << ": vec4<f32>,"
<< std::endl;
}
uint32_t lastComponentCount = totalUserDefinedInterStageShaderComponentCount % 4;
if (lastComponentCount > 0) {
stream << "@location(" << vec4InputLocations << ") var" << vec4InputLocations << ": ";
if (lastComponentCount == 1) {
stream << "f32,";
} else {
stream << " vec" << lastComponentCount << "<f32>,";
}
stream << std::endl;
}
if (failingShaderStage == wgpu::ShaderStage::Vertex) {
stream << " @builtin(position) pos: vec4<f32>,";
}
stream << "}\n";
std::string ioStruct = stream.str();
// Build the test pipeline. Note that it's not possible with just ASSERT_DEVICE_ERROR
// whether it is the vertex or fragment shader that fails. So instead we will look for the
// string "failingVertex" or "failingFragment" in the error message.
utils::ComboRenderPipelineDescriptor pDesc;
pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
if (usePointListAsPrimitiveType) {
pDesc.primitive.topology = wgpu::PrimitiveTopology::PointList;
} else {
pDesc.primitive.topology = wgpu::PrimitiveTopology::TriangleList;
}
const char* errorMatcher = nullptr;
switch (failingShaderStage) {
case wgpu::ShaderStage::Vertex: {
if (usePointListAsPrimitiveType) {
errorMatcher = "PointList";
} else {
errorMatcher = "failingVertex";
}
pDesc.vertex.entryPoint = "failingVertex";
pDesc.vertex.module = utils::CreateShaderModule(device, (ioStruct + R"(
@vertex fn failingVertex() -> ShaderIO {
var shaderIO : ShaderIO;
shaderIO.pos = vec4<f32>(0.0, 0.0, 0.0, 1.0);
return shaderIO;
}
)")
.c_str());
pDesc.cFragment.module = utils::CreateShaderModule(device, R"(
@fragment fn main() -> @location(0) vec4<f32> {
return vec4<f32>(0.0);
}
)");
break;
}
case wgpu::ShaderStage::Fragment: {
errorMatcher = "failingFragment";
pDesc.cFragment.entryPoint = "failingFragment";
pDesc.cFragment.module = utils::CreateShaderModule(device, (ioStruct + R"(
@fragment fn failingFragment(io : ShaderIO) -> @location(0) vec4<f32> {
return vec4<f32>(0.0);
}
)")
.c_str());
pDesc.vertex.module = utils::CreateShaderModule(device, R"(
@vertex fn main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.0);
}
)");
break;
}
default:
UNREACHABLE();
}
if (success) {
if (failingShaderStage == wgpu::ShaderStage::Vertex) {
// It is allowed that fragment inputs are a subset of the vertex output variables.
device.CreateRenderPipeline(&pDesc);
} else {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pDesc),
testing::HasSubstr("The fragment input at location"));
}
} else {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&pDesc),
testing::HasSubstr(errorMatcher));
}
};
// Verify when there is no input builtin variable in a fragment shader, the total user-defined
// input component count must be less than kMaxInterStageShaderComponents.
{
CheckTestPipeline(true, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment);
CheckTestPipeline(false, kMaxInterStageShaderComponents + 1, wgpu::ShaderStage::Fragment);
}
// Verify the total user-defined vertex output component count must be less than
// kMaxInterStageShaderComponents.
{
CheckTestPipeline(true, kMaxInterStageShaderComponents, wgpu::ShaderStage::Vertex);
CheckTestPipeline(false, kMaxInterStageShaderComponents + 1, wgpu::ShaderStage::Vertex);
}
// Verify the total user-defined vertex output component count must be less than
// (kMaxInterStageShaderComponents - 1) when the primitive topology is PointList.
{
constexpr bool kUsePointListAsPrimitiveTopology = true;
const char* kExtraBuiltins = "";
CheckTestPipeline(true, kMaxInterStageShaderComponents - 1, wgpu::ShaderStage::Vertex,
kExtraBuiltins, kUsePointListAsPrimitiveTopology);
CheckTestPipeline(false, kMaxInterStageShaderComponents, wgpu::ShaderStage::Vertex,
kExtraBuiltins, kUsePointListAsPrimitiveTopology);
}
// @builtin(position) in fragment shaders shouldn't be counted into the maximum inter-stage
// component count.
{
CheckTestPipeline(true, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment,
"@builtin(position) fragCoord : vec4<f32>,");
}
// @builtin(front_facing) should be counted into the maximum inter-stage component count.
{
CheckTestPipeline(true, kMaxInterStageShaderComponents - 1, wgpu::ShaderStage::Fragment,
"@builtin(front_facing) frontFacing : bool,");
CheckTestPipeline(false, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment,
"@builtin(front_facing) frontFacing : bool,");
}
// @builtin(sample_index) should be counted into the maximum inter-stage component count.
{
CheckTestPipeline(true, kMaxInterStageShaderComponents - 1, wgpu::ShaderStage::Fragment,
"@builtin(sample_index) sampleIndex : u32,");
CheckTestPipeline(false, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment,
"@builtin(sample_index) sampleIndex : u32,");
}
// @builtin(sample_mask) should be counted into the maximum inter-stage component count.
{
CheckTestPipeline(true, kMaxInterStageShaderComponents - 1, wgpu::ShaderStage::Fragment,
"@builtin(sample_mask) sampleMask : u32,");
CheckTestPipeline(false, kMaxInterStageShaderComponents, wgpu::ShaderStage::Fragment,
"@builtin(sample_mask) sampleMask : u32,");
}
}
// Test that numeric ID must be unique
TEST_F(ShaderModuleValidationTest, OverridableConstantsNumericIDConflicts) {
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
@id(1234) override c0: u32;
@id(1234) override c1: u32;
struct Buf {
data : array<u32, 2>
}
@group(0) @binding(0) var<storage, read_write> buf : Buf;
@compute @workgroup_size(1) fn main() {
// make sure the overridable constants are not optimized out
buf.data[0] = c0;
buf.data[1] = c1;
})"));
}
// Test that @binding must be less then kMaxBindingsPerBindGroup
TEST_F(ShaderModuleValidationTest, MaxBindingNumber) {
static_assert(kMaxBindingsPerBindGroup == 640);
wgpu::ComputePipelineDescriptor desc;
desc.compute.entryPoint = "main";
// kMaxBindingsPerBindGroup-1 is valid.
desc.compute.module = utils::CreateShaderModule(device, R"(
@group(0) @binding(639) var s : sampler;
@compute @workgroup_size(1) fn main() {
_ = s;
}
)");
device.CreateComputePipeline(&desc);
// kMaxBindingsPerBindGroup is an error
desc.compute.module = utils::CreateShaderModule(device, R"(
@group(0) @binding(640) var s : sampler;
@compute @workgroup_size(1) fn main() {
_ = s;
}
)");
ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&desc));
}
// Test that missing decorations on shader IO or bindings causes a validation error.
TEST_F(ShaderModuleValidationTest, MissingDecorations) {
// Vertex input.
utils::CreateShaderModule(device, R"(
@vertex fn main(@location(0) a : vec4<f32>) -> @builtin(position) vec4<f32> {
return vec4(1.0);
}
)");
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
@vertex fn main(a : vec4<f32>) -> @builtin(position) vec4<f32> {
return vec4(1.0);
}
)"));
// Vertex output
utils::CreateShaderModule(device, R"(
struct Output {
@builtin(position) pos : vec4<f32>,
@location(0) a : f32,
}
@vertex fn main() -> Output {
var output : Output;
return output;
}
)");
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
struct Output {
@builtin(position) pos : vec4<f32>,
a : f32,
}
@vertex fn main() -> Output {
var output : Output;
return output;
}
)"));
// Fragment input
utils::CreateShaderModule(device, R"(
@fragment fn main(@location(0) a : vec4<f32>) -> @location(0) f32 {
return 1.0;
}
)");
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
@fragment fn main(a : vec4<f32>) -> @location(0) f32 {
return 1.0;
}
)"));
// Fragment input
utils::CreateShaderModule(device, R"(
@fragment fn main() -> @location(0) f32 {
return 1.0;
}
)");
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
@fragment fn main() -> f32 {
return 1.0;
}
)"));
// Binding decorations
utils::CreateShaderModule(device, R"(
@group(0) @binding(0) var s : sampler;
@fragment fn main() -> @location(0) f32 {
_ = s;
return 1.0;
}
)");
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
@binding(0) var s : sampler;
@fragment fn main() -> @location(0) f32 {
_ = s;
return 1.0;
}
)"));
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
@group(0) var s : sampler;
@fragment fn main() -> @location(0) f32 {
_ = s;
return 1.0;
}
)"));
}
// Test that WGSL extension used by enable directives must be allowed by WebGPU.
TEST_F(ShaderModuleValidationTest, ExtensionMustBeAllowed) {
ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
enable f16;
@compute @workgroup_size(1) fn main() {})"));
}