blob: 0aaacc5caa6883fd78953a9b4266cf691018065c [file] [log] [blame]
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <algorithm>
#include <limits>
#include <sstream>
#include <string>
#include <vector>
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "dawn/tests/unittests/validation/ValidationTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
namespace {
class CompatValidationTest : public ValidationTest {
protected:
bool UseCompatibilityMode() const override { return true; }
};
TEST_F(CompatValidationTest, CanNotCreateCubeArrayTextureView) {
wgpu::TextureDescriptor descriptor;
descriptor.size = {1, 1, 6};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture cubeTexture = device.CreateTexture(&descriptor);
{
wgpu::TextureViewDescriptor cubeViewDescriptor;
cubeViewDescriptor.dimension = wgpu::TextureViewDimension::Cube;
cubeViewDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
cubeTexture.CreateView(&cubeViewDescriptor);
}
{
wgpu::TextureViewDescriptor cubeArrayViewDescriptor;
cubeArrayViewDescriptor.dimension = wgpu::TextureViewDimension::CubeArray;
cubeArrayViewDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
ASSERT_DEVICE_ERROR(cubeTexture.CreateView(&cubeArrayViewDescriptor));
}
cubeTexture.Destroy();
}
TEST_F(CompatValidationTest, CanNotSpecifyAlternateCompatibleViewFormatRGBA8Unorm) {
constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb;
wgpu::TextureDescriptor descriptor;
descriptor.size = {1, 1, 1};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
descriptor.viewFormatCount = 1;
descriptor.viewFormats = &viewFormat;
wgpu::Texture texture;
ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor),
testing::HasSubstr("must match format"));
texture.Destroy();
}
TEST_F(CompatValidationTest, CanNotSpecifyAlternateCompatibleViewFormatRGBA8UnormSrgb) {
constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8Unorm;
wgpu::TextureDescriptor descriptor;
descriptor.size = {1, 1, 1};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::RGBA8UnormSrgb;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
descriptor.viewFormatCount = 1;
descriptor.viewFormats = &viewFormat;
wgpu::Texture texture;
ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor),
testing::HasSubstr("must match format"));
texture.Destroy();
}
TEST_F(CompatValidationTest, CanNotCreatePipelineWithDifferentPerTargetBlendStateOrWriteMask) {
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
@vertex fn vs() -> @builtin(position) vec4f {
return vec4f(0);
}
struct FragmentOut {
@location(0) fragColor0 : vec4f,
@location(1) fragColor1 : vec4f,
@location(2) fragColor2 : vec4f,
}
@fragment fn fs() -> FragmentOut {
var output : FragmentOut;
output.fragColor0 = vec4f(0);
output.fragColor1 = vec4f(0);
output.fragColor2 = vec4f(0);
return output;
}
)");
utils::ComboRenderPipelineDescriptor testDescriptor;
testDescriptor.layout = {};
testDescriptor.vertex.module = module;
testDescriptor.cFragment.module = module;
testDescriptor.cFragment.targetCount = 3;
testDescriptor.cTargets[1].format = wgpu::TextureFormat::Undefined;
for (int i = 0; i < 10; ++i) {
wgpu::BlendState blend0;
wgpu::BlendState blend2;
// Blend state intentionally omitted for target 1
testDescriptor.cTargets[0].blend = &blend0;
testDescriptor.cTargets[2].blend = &blend2;
bool expectError = true;
switch (i) {
case 0: // default
expectError = false;
break;
case 1: // no blend
testDescriptor.cTargets[0].blend = nullptr;
break;
case 2: // no blend second target
testDescriptor.cTargets[2].blend = nullptr;
break;
case 3: // color.operation
blend2.color.operation = wgpu::BlendOperation::Subtract;
break;
case 4: // color.srcFactor
blend2.color.srcFactor = wgpu::BlendFactor::SrcAlpha;
break;
case 5: // color.dstFactor
blend2.color.dstFactor = wgpu::BlendFactor::DstAlpha;
break;
case 6: // alpha.operation
blend2.alpha.operation = wgpu::BlendOperation::Subtract;
break;
case 7: // alpha.srcFactor
blend2.alpha.srcFactor = wgpu::BlendFactor::SrcAlpha;
break;
case 8: // alpha.dstFactor
blend2.alpha.dstFactor = wgpu::BlendFactor::DstAlpha;
break;
case 9: // writeMask
testDescriptor.cTargets[2].writeMask = wgpu::ColorWriteMask::Green;
break;
default:
DAWN_UNREACHABLE();
}
if (expectError) {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&testDescriptor));
} else {
device.CreateRenderPipeline(&testDescriptor);
}
}
}
TEST_F(CompatValidationTest, CanNotCreatePipelineWithNonZeroDepthBiasClamp) {
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
@vertex fn vs() -> @builtin(position) vec4f {
return vec4f(0);
}
@fragment fn fs() -> @location(0) vec4f {
return vec4f(0);
}
)");
utils::ComboRenderPipelineDescriptor testDescriptor;
testDescriptor.layout = {};
testDescriptor.vertex.module = module;
testDescriptor.cFragment.module = module;
testDescriptor.cFragment.targetCount = 1;
testDescriptor.cTargets[1].format = wgpu::TextureFormat::RGBA8Unorm;
wgpu::DepthStencilState* depthStencil =
testDescriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24Plus);
depthStencil->depthWriteEnabled = wgpu::OptionalBool::True;
depthStencil->depthBias = 0;
depthStencil->depthBiasSlopeScale = 0;
depthStencil->depthBiasClamp = 0;
device.CreateRenderPipeline(&testDescriptor);
depthStencil->depthBiasClamp = 1;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&testDescriptor));
}
TEST_F(CompatValidationTest, CanNotCreatePipelineWithTextureLoadOfDepthTexture) {
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
@group(0) @binding(0) var<storage, read_write> dstBuf : array<vec4f>;
@group(0) @binding(1) var tex1 : texture_2d<f32>;
@group(0) @binding(2) var tex2 : texture_depth_2d;
@group(0) @binding(3) var tex3 : texture_depth_2d_array;
@group(0) @binding(4) var tex4 : texture_depth_multisampled_2d;
@compute @workgroup_size(1) fn main1() {
dstBuf[0] = textureLoad(tex1, vec2(0), 0);
}
@compute @workgroup_size(1) fn main2() {
dstBuf[0] = vec4f(textureLoad(tex2, vec2(0), 0));
}
@compute @workgroup_size(1) fn main3() {
dstBuf[0] = vec4f(textureLoad(tex3, vec2(0), 0, 0));
}
@compute @workgroup_size(1) fn main4() {
dstBuf[4] = vec4f(textureLoad(tex4, vec2(0), 0));
}
)");
const char* entryPoints[] = {"main1", "main2", "main3", "main4"};
for (auto entryPoint : entryPoints) {
wgpu::ComputePipelineDescriptor pDesc;
pDesc.compute.module = module;
pDesc.compute.entryPoint = entryPoint;
if (entryPoint == entryPoints[0]) {
device.CreateComputePipeline(&pDesc);
} else {
ASSERT_DEVICE_ERROR(
device.CreateComputePipeline(&pDesc),
testing::HasSubstr(
"textureLoad can not be used with depth textures in compatibility mode"));
}
}
}
TEST_F(CompatValidationTest, CanNotCreatePipelineWithDepthTextureUsedWithNonComparisonSampler) {
wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
@group(1) @binding(0) var s: sampler;
@group(1) @binding(1) var sc: sampler_comparison;
@group(0) @binding(0) var tex2d : texture_depth_2d;
@group(0) @binding(1) var tex2dArray: texture_depth_2d_array;
@group(0) @binding(2) var texCube : texture_depth_cube;
@group(0) @binding(3) var texCubeArray : texture_depth_cube_array;
@vertex fn vs() -> @builtin(position) vec4f {
return vec4f(0);
}
// valid
@fragment fn main0() -> @location(0) vec4f {
return textureGatherCompare(tex2d, sc, vec2(0), 0) +
textureGatherCompare(tex2dArray, sc, vec2(0), 0, 0) +
textureGatherCompare(texCube, sc, vec3(0), 0) +
textureGatherCompare(texCubeArray, sc, vec3(0), 0, 0) +
vec4f(textureSampleCompare(tex2d, sc, vec2(0), 0)) +
vec4f(textureSampleCompare(tex2dArray, sc, vec2(0), 0, 0)) +
vec4f(textureSampleCompare(texCube, sc, vec3(0), 0)) +
vec4f(textureSampleCompare(texCubeArray, sc, vec3(0), 0, 0)) +
vec4f(textureSampleCompareLevel(tex2d, sc, vec2(0), 0)) +
vec4f(textureSampleCompareLevel(tex2dArray, sc, vec2(0), 0, 0)) +
vec4f(textureSampleCompareLevel(texCube, sc, vec3(0), 0)) +
vec4f(textureSampleCompareLevel(texCubeArray, sc, vec3(0), 0, 0)) ;
}
@fragment fn main1() -> @location(0) vec4f {
return textureGather(tex2d, s, vec2(0));
}
@fragment fn main2() -> @location(0) vec4f {
return textureGather(tex2dArray, s, vec2(0), 0);
}
@fragment fn main3() -> @location(0) vec4f {
return textureGather(texCube, s, vec3(0));
}
@fragment fn main4() -> @location(0) vec4f {
return textureGather(texCubeArray, s, vec3(0), 0);
}
@fragment fn main5() -> @location(0) vec4f {
return vec4f(textureSample(tex2d, s, vec2(0)));
}
@fragment fn main6() -> @location(0) vec4f {
return vec4f(textureSample(tex2dArray, s, vec2(0), 0));
}
@fragment fn main7() -> @location(0) vec4f {
return vec4f(textureSample(texCube, s, vec3(0)));
}
@fragment fn main8() -> @location(0) vec4f {
return vec4f(textureSample(texCubeArray, s, vec3(0), 0));
}
@fragment fn main9() -> @location(0) vec4f {
return vec4f(textureSampleLevel(tex2d, s, vec2(0), 0));
}
@fragment fn main10() -> @location(0) vec4f {
return vec4f(textureSampleLevel(tex2dArray, s, vec2(0), 0, 0));
}
@fragment fn main11() -> @location(0) vec4f {
return vec4f(textureSampleLevel(texCube, s, vec3(0), 0));
}
@fragment fn main12() -> @location(0) vec4f {
return vec4f(textureSampleLevel(texCubeArray, s, vec3(0), 0, 0));
}
)");
const char* entryPoints[] = {"main0", "main1", "main2", "main3", "main4", "main5", "main6",
"main7", "main8", "main9", "main10", "main11", "main12"};
for (auto entryPoint : entryPoints) {
utils::ComboRenderPipelineDescriptor pDesc;
pDesc.vertex.module = module;
pDesc.cFragment.module = module;
pDesc.cFragment.entryPoint = entryPoint;
pDesc.cFragment.targetCount = 1;
pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
if (entryPoint == entryPoints[0]) {
device.CreateRenderPipeline(&pDesc);
} else {
ASSERT_DEVICE_ERROR(
device.CreateRenderPipeline(&pDesc),
testing::HasSubstr("texture_depth_xx can not be used with non-comparison samplers "
"in compatibility mode"));
}
}
}
TEST_F(CompatValidationTest, CanNotUseTooManyTextureSamplerCombos) {
wgpu::Limits limits;
device.GetLimits(&limits);
uint32_t maxCombos =
std::min(limits.maxSampledTexturesPerShaderStage, limits.maxSamplersPerShaderStage);
struct Test {
bool expectSuccess;
uint32_t numCombos;
uint32_t numNonSamplerUsages;
uint32_t numExternalTextures;
bool useSameExternalTexture;
wgpu::ShaderStage stages;
};
// clang-format off
Test comboTests[] = {
// num use
// non num same
// sampler external external
// pass numCombos uses textures tex stage
{true , maxCombos , 0 , 0 , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment},
{true , 1 , maxCombos, 0 , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment},
{false, 2 , maxCombos, 0 , false, wgpu::ShaderStage::Vertex | wgpu::ShaderStage::Fragment},
{true , maxCombos - 4, 0 , 1 , false, wgpu::ShaderStage::Vertex},
{false, maxCombos - 3, 0 , 1 , false, wgpu::ShaderStage::Vertex},
{true , maxCombos - 8, 0 , 2 , false, wgpu::ShaderStage::Vertex},
{false, maxCombos - 7, 0 , 2 , false, wgpu::ShaderStage::Vertex},
{true , maxCombos - 7, 0 , 2 , true, wgpu::ShaderStage::Vertex},
{false, maxCombos - 6, 0 , 2 , true, wgpu::ShaderStage::Vertex},
{true , maxCombos - 4, 0 , 1 , false, wgpu::ShaderStage::Fragment},
{false, maxCombos - 3, 0 , 1 , false, wgpu::ShaderStage::Fragment},
{true , maxCombos - 8, 0 , 2 , false, wgpu::ShaderStage::Fragment},
{false, maxCombos - 7, 0 , 2 , false, wgpu::ShaderStage::Fragment},
{true , maxCombos - 7, 0 , 2 , true, wgpu::ShaderStage::Fragment},
{false, maxCombos - 6, 0 , 2 , true, wgpu::ShaderStage::Fragment},
{false, maxCombos + 1, 0 , 0 , false, wgpu::ShaderStage::Vertex},
{false, maxCombos + 1, 0 , 0 , false, wgpu::ShaderStage::Fragment},
};
// clang-format on
for (const auto& test : comboTests) {
uint32_t maxTexturesPerShaderStage =
limits.maxSampledTexturesPerShaderStage - (test.numExternalTextures * 3);
auto numCombos = test.numCombos;
std::vector<std::string> textureDeclarations[2];
std::vector<std::string> samplerDeclarations[2];
std::vector<std::string> usages[2];
for (uint32_t stage = 0; stage < 2; ++stage) {
uint32_t count = 0;
for (uint32_t t = 0; count < numCombos && t < maxTexturesPerShaderStage; ++t) {
textureDeclarations[stage].push_back(
absl::StrFormat("@group(%u) @binding(%u) var t%u_%u: texture_2d<f32>;",
stage * 2, t, stage, t));
for (uint32_t s = 0; count < numCombos && t < limits.maxSamplersPerShaderStage;
++s) {
if (t == 0) {
samplerDeclarations[stage].push_back(
absl::StrFormat("@group(%u) @binding(%u) var s%u_%u: sampler;",
(stage * 2) + 1, s, stage, s));
}
usages[stage].push_back(
absl::StrFormat("c += textureSampleLevel(t%u_%u, s%u_%u, vec2f(0), 0);",
stage, t, stage, s));
++count;
}
}
for (uint32_t t = 0; t < test.numNonSamplerUsages; ++t) {
if (t >= textureDeclarations[stage].size()) {
textureDeclarations[stage].push_back(
absl::StrFormat("@group(%u) @binding(%u) var t%u_%u: texture_2d<f32>;",
stage * 2, t, stage, t));
}
usages[stage].push_back(
absl::StrFormat("c += textureLoad(t%u_%u, vec2u(0), 0);", stage, t));
}
for (uint32_t t = 0; t < test.numExternalTextures; ++t) {
if (t == 0 || !test.useSameExternalTexture) {
auto et = textureDeclarations[stage].size() + t;
textureDeclarations[stage].push_back(
absl::StrFormat("@group(%u) @binding(%u) var e%u_%u: texture_external;",
stage * 2, et, stage, t));
}
usages[stage].push_back(
absl::StrFormat("c += textureSampleBaseClampToEdge(e%u_%u, s%u_%u, vec2f(0));",
stage, test.useSameExternalTexture ? 0 : t, stage,
test.useSameExternalTexture ? t : 0));
}
}
auto wgsl =
absl::StrFormat(R"(
%s
%s
%s
%s
fn usage0() -> vec4f {
var c: vec4f;
%s
return c;
}
fn usage1() -> vec4f {
var c: vec4f;
%s
return c;
}
@vertex fn vs() -> @builtin(position) vec4f {
_ = %s;
return vec4f(0);
}
@group(2) @binding(0) var tt: texture_2d<f32>;
@fragment fn fs() -> @location(0) vec4f {
return %s;
}
)",
absl::StrJoin(textureDeclarations[0], "\n"),
absl::StrJoin(samplerDeclarations[0], "\n"),
absl::StrJoin(textureDeclarations[1], "\n"),
absl::StrJoin(samplerDeclarations[1], "\n"),
absl::StrJoin(usages[0], "\n "), absl::StrJoin(usages[1], "\n "),
test.stages & wgpu::ShaderStage::Vertex ? "usage0()" : "vec4f(0)",
test.stages & wgpu::ShaderStage::Fragment ? "usage1()" : "vec4f(0)");
wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl.c_str());
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = module;
descriptor.cFragment.module = module;
descriptor.cFragment.targetCount = 1;
descriptor.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
if (test.expectSuccess) {
device.CreateRenderPipeline(&descriptor);
} else {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
testing::HasSubstr("compat"));
}
}
}
TEST_F(CompatValidationTest, CanNotUseSampleMask) {
wgpu::ShaderModule moduleSampleMaskOutput = utils::CreateShaderModule(device, R"(
@vertex fn vs() -> @builtin(position) vec4f {
return vec4f(1);
}
struct Output {
@builtin(sample_mask) mask_out: u32,
@location(0) color : vec4f,
}
@fragment fn fsWithoutSampleMaskUsage() -> @location(0) vec4f {
return vec4f(1.0, 1.0, 1.0, 1.0);
}
@fragment fn fsWithSampleMaskUsage() -> Output {
var o: Output;
// We need to make sure this sample_mask isn't optimized out even its value equals "no op".
o.mask_out = 0xFFFFFFFFu;
o.color = vec4f(1.0, 1.0, 1.0, 1.0);
return o;
}
)");
// Check we can use a fragment shader that doesn't use sample_mask from
// the same module as one that does.
{
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = moduleSampleMaskOutput;
descriptor.cFragment.module = moduleSampleMaskOutput;
descriptor.cFragment.entryPoint = "fsWithoutSampleMaskUsage";
descriptor.multisample.count = 4;
descriptor.multisample.alphaToCoverageEnabled = false;
device.CreateRenderPipeline(&descriptor);
}
// Check we can not use a fragment shader that uses sample_mask.
{
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = moduleSampleMaskOutput;
descriptor.cFragment.module = moduleSampleMaskOutput;
descriptor.cFragment.entryPoint = "fsWithSampleMaskUsage";
descriptor.multisample.count = 4;
descriptor.multisample.alphaToCoverageEnabled = false;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
testing::HasSubstr("sample_mask"));
}
}
TEST_F(CompatValidationTest, CanNotUseFragmentShaderWithSampleIndex) {
wgpu::ShaderModule moduleSampleMaskOutput = utils::CreateShaderModule(device, R"(
@vertex fn vs() -> @builtin(position) vec4f {
return vec4f(1);
}
struct Output {
@location(0) color : vec4f,
}
@fragment fn fsWithoutSampleIndexUsage() -> @location(0) vec4f {
return vec4f(1.0, 1.0, 1.0, 1.0);
}
@fragment fn fsWithSampleIndexUsage(@builtin(sample_index) sNdx: u32) -> Output {
var o: Output;
_ = sNdx;
o.color = vec4f(1.0, 1.0, 1.0, 1.0);
return o;
}
)");
// Check we can use a fragment shader that doesn't use sample_index from
// the same module as one that does.
{
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = moduleSampleMaskOutput;
descriptor.vertex.entryPoint = "vs";
descriptor.cFragment.module = moduleSampleMaskOutput;
descriptor.cFragment.entryPoint = "fsWithoutSampleIndexUsage";
descriptor.multisample.count = 4;
descriptor.multisample.alphaToCoverageEnabled = false;
device.CreateRenderPipeline(&descriptor);
}
// Check we can not use a fragment shader that uses sample_index.
{
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = moduleSampleMaskOutput;
descriptor.vertex.entryPoint = "vs";
descriptor.cFragment.module = moduleSampleMaskOutput;
descriptor.cFragment.entryPoint = "fsWithSampleIndexUsage";
descriptor.multisample.count = 4;
descriptor.multisample.alphaToCoverageEnabled = false;
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
testing::HasSubstr("sample_index"));
}
}
TEST_F(CompatValidationTest, CanNotUseShaderWithUnsupportedInterpolateTypeOrSampling) {
static const char* interpolateParams[] = {
"perspective", // should pass
"linear", "perspective, sample", "flat", "flat, first",
};
for (auto interpolateParam : interpolateParams) {
auto wgsl = absl::StrFormat(R"(
struct Vertex {
@builtin(position) pos: vec4f,
@location(0) @interpolate(%s) color : vec4f,
};
@vertex fn vs() -> Vertex {
var v: Vertex;
v.pos = vec4f(1);
v.color = vec4f(1);
return v;
}
@fragment fn fsWithoutBadInterpolationUsage() -> @location(0) vec4f {
return vec4f(1);
}
@fragment fn fsWithBadInterpolationUsage1(v: Vertex) -> @location(0) vec4f {
return vec4f(1);
}
@fragment fn fsWithBadInterpolationUsage2(v: Vertex) -> @location(0) vec4f {
return v.pos;
}
@fragment fn fsWithBadInterpolationUsage3(v: Vertex) -> @location(0) vec4f {
return v.color;
}
)",
interpolateParam);
wgpu::ShaderModule moduleInterpolationLinear =
utils::CreateShaderModule(device, wgsl.c_str());
static const char* entryPoints[] = {
"fsWithoutBadInterpolationUsage",
"fsWithBadInterpolationUsage1",
"fsWithBadInterpolationUsage2",
"fsWithBadInterpolationUsage3",
};
for (auto entryPoint : entryPoints) {
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.vertex.module = moduleInterpolationLinear;
descriptor.cFragment.module = moduleInterpolationLinear;
descriptor.cFragment.entryPoint = entryPoint;
bool shouldSucceed =
entryPoint == entryPoints[0] || interpolateParam == interpolateParams[0];
if (shouldSucceed) {
device.CreateRenderPipeline(&descriptor);
} else {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
testing::HasSubstr("in compatibility mode"));
}
}
}
}
TEST_F(CompatValidationTest, CanNotCreateRGxxxStorageTexture) {
const wgpu::TextureFormat formats[] = {
wgpu::TextureFormat::RGBA8Unorm, // pass check
wgpu::TextureFormat::RG32Sint,
wgpu::TextureFormat::RG32Uint,
wgpu::TextureFormat::RG32Float,
};
for (auto format : formats) {
wgpu::TextureDescriptor descriptor;
descriptor.size = {1, 1, 1};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = format;
descriptor.usage = wgpu::TextureUsage::StorageBinding;
wgpu::Texture texture;
if (format == wgpu::TextureFormat::RGBA8Unorm) {
texture = device.CreateTexture(&descriptor);
} else {
ASSERT_DEVICE_ERROR(texture = device.CreateTexture(&descriptor));
}
texture.Destroy();
}
}
TEST_F(CompatValidationTest, CanNotUseStorageBufferInVertexStageWithDefaultLimit0) {
const wgpu::ShaderStage stages[] = {
wgpu::ShaderStage::Compute,
wgpu::ShaderStage::Fragment,
wgpu::ShaderStage::Vertex,
};
const wgpu::BufferBindingType buffer_types[] = {
wgpu::BufferBindingType::Storage,
wgpu::BufferBindingType::ReadOnlyStorage,
};
for (auto stage : stages) {
for (auto buffer_type : buffer_types) {
if (stage == wgpu::ShaderStage::Vertex &&
buffer_type == wgpu::BufferBindingType::Storage) {
continue;
}
wgpu::BindGroupLayoutEntry entries[1];
entries[0].binding = 0;
entries[0].visibility = stage;
entries[0].buffer.type = buffer_type;
wgpu::BindGroupLayoutDescriptor descriptor;
descriptor.entryCount = 1;
descriptor.entries = entries;
if (stage != wgpu::ShaderStage::Vertex) {
wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&descriptor);
} else {
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor),
testing::HasSubstr("maxStorageBuffers"));
}
}
}
}
TEST_F(CompatValidationTest, CanNotUseStorageTexturesInVertexStageWithDefaultLimit0) {
const wgpu::ShaderStage stages[] = {
wgpu::ShaderStage::Compute,
wgpu::ShaderStage::Fragment,
wgpu::ShaderStage::Vertex,
};
const wgpu::StorageTextureAccess access_types[] = {
wgpu::StorageTextureAccess::ReadOnly,
wgpu::StorageTextureAccess::ReadWrite,
wgpu::StorageTextureAccess::WriteOnly,
};
for (auto stage : stages) {
for (auto access : access_types) {
if (stage == wgpu::ShaderStage::Vertex &&
(access == wgpu::StorageTextureAccess::ReadWrite ||
access == wgpu::StorageTextureAccess::WriteOnly)) {
continue;
}
wgpu::BindGroupLayoutEntry entries[1];
entries[0].binding = 0;
entries[0].visibility = stage;
entries[0].storageTexture.format = wgpu::TextureFormat::R32Float;
entries[0].storageTexture.access = access;
wgpu::BindGroupLayoutDescriptor descriptor;
descriptor.entryCount = 1;
descriptor.entries = entries;
if (stage != wgpu::ShaderStage::Vertex) {
wgpu::BindGroupLayout layout = device.CreateBindGroupLayout(&descriptor);
} else {
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor),
testing::HasSubstr("maxStorageTextures"));
}
}
}
}
constexpr const char* kRenderTwoTexturesOneBindgroupWGSL = R"(
@vertex
fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
var pos = array(
vec4f(-1, 3, 0, 1),
vec4f( 3, -1, 0, 1),
vec4f(-1, -1, 0, 1));
return pos[VertexIndex];
}
@group(0) @binding(0) var tex0 : texture_2d<f32>;
@group(0) @binding(1) var tex1 : texture_2d<f32>;
@fragment
fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
_ = tex0;
_ = tex1;
return vec4f(0);
}
)";
constexpr const char* kRenderTwoTexturesTwoBindgroupsWGSL = R"(
@vertex
fn vs(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
var pos = array(
vec4f(-1, 3, 0, 1),
vec4f( 3, -1, 0, 1),
vec4f(-1, -1, 0, 1));
return pos[VertexIndex];
}
@group(0) @binding(0) var tex0 : texture_2d<f32>;
@group(1) @binding(0) var tex1 : texture_2d<f32>;
@fragment
fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
_ = tex0;
_ = tex1;
return vec4f(0);
}
)";
void TestMultipleTextureViewValidationInRenderPass(
wgpu::Device device,
const char* wgsl,
std::function<void(wgpu::Device device,
wgpu::Texture texture,
wgpu::RenderPipeline pipeline,
std::function<void(wgpu::RenderPassEncoder pass)> drawFn)> fn) {
wgpu::TextureDescriptor descriptor;
descriptor.size = {2, 1, 1};
descriptor.mipLevelCount = 2;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
wgpu::Texture texture = device.CreateTexture(&descriptor);
constexpr uint32_t indices[] = {0, 1, 2};
wgpu::Buffer indexBuffer =
utils::CreateBufferFromData(device, indices, sizeof indices, wgpu::BufferUsage::Index);
// Create a pipeline that will sample from 2 2D textures and output to an attachment.
wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl);
utils::ComboRenderPipelineDescriptor pDesc;
pDesc.vertex.module = module;
pDesc.cFragment.module = module;
pDesc.cTargets[0].format = wgpu::TextureFormat::RGBA8Unorm;
wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pDesc);
fn(device, texture, pipeline, [](wgpu::RenderPassEncoder pass) { pass.Draw(3); });
fn(device, texture, pipeline, [indexBuffer](wgpu::RenderPassEncoder pass) {
pass.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
pass.DrawIndexed(3);
});
indexBuffer.Destroy();
texture.Destroy();
}
enum FlexibleTextureViewsFeature {
Enabled,
Disabled,
};
class CompatTextureViewValidationTests
: public CompatValidationTest,
public ::testing::WithParamInterface<FlexibleTextureViewsFeature> {
public:
static std::string PrintToStringParamName(
const testing::TestParamInfo<FlexibleTextureViewsFeature>& info) {
std::ostringstream ss;
ss << "WithFlexibleTextureViews";
if (info.param == FlexibleTextureViewsFeature::Enabled) {
ss << "Enabled";
} else {
ss << "Disabled";
}
return ss.str();
}
protected:
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
if (HasFlexibleTextureViews()) {
return {wgpu::FeatureName::FlexibleTextureViews};
}
return {};
}
bool HasFlexibleTextureViews() const {
return GetParam() == FlexibleTextureViewsFeature::Enabled;
}
};
#define ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(statement, matcher) \
do { \
if (HasFlexibleTextureViews()) { \
statement; \
} else { \
ASSERT_DEVICE_ERROR(statement, matcher); \
} \
} while (0)
// Test we get a validation error if we have 2 different views of a texture
// in the same bind group. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests, CanNotDrawDifferentMipsSameTextureSameBindGroup) {
TestMultipleTextureViewValidationInRenderPass(
device, kRenderTwoTexturesOneBindgroupWGSL,
[this](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
wgpu::BindGroup bindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
drawFn(pass);
pass.End();
ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
testing::HasSubstr("different views"));
});
}
// Test we get a validation error if we have 2 different views of a texture spanning
// different bind groups. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests, CanNotDrawDifferentMipsSameTextureDifferentBindGroups) {
TestMultipleTextureViewValidationInRenderPass(
device, kRenderTwoTexturesTwoBindgroupsWGSL,
[this](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup0);
pass.SetBindGroup(1, bindGroup1);
drawFn(pass);
pass.End();
ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
testing::HasSubstr("different views"));
});
}
// Test that it's possible to set a bindgroup that uses a texture with multiple views
// which would be an error if you issued a draw command but, you then fix the issue by replacing
// the bindgroup with one that does not have multiple views. We're trying to test
// that the implementation does the validation at draw command time and not before.
TEST_P(CompatTextureViewValidationTests,
CanBindDifferentMipsSameTextureSameBindGroupAndFixWithoutError) {
TestMultipleTextureViewValidationInRenderPass(
device, kRenderTwoTexturesOneBindgroupWGSL,
[](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
// Bindgroup with different views of same texture
wgpu::BindGroup badBindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});
// Bindgroup with same views of texture
wgpu::BindGroup goodBindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip0ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, badBindGroup);
pass.SetBindGroup(0, goodBindGroup);
drawFn(pass);
pass.End();
// No Error is expected
encoder.Finish();
});
}
// Test that having 2 texture views that have the same settings, in 2 different
// bindgroups, does not generate a validation error.
TEST_P(CompatTextureViewValidationTests, CanBindSameViewIn2BindGroups) {
TestMultipleTextureViewValidationInRenderPass(
device, kRenderTwoTexturesTwoBindgroupsWGSL,
[](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});
// Bindgroup with same view of texture as bindGroup0
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup0);
pass.SetBindGroup(1, bindGroup1);
drawFn(pass);
pass.End();
// No Error is expected
encoder.Finish();
});
}
// Test that no validation error happens if we have multiple views of a texture
// but don't draw.
TEST_P(CompatTextureViewValidationTests, NoErrorIfMultipleDifferentViewsOfTextureAreNotUsed) {
TestMultipleTextureViewValidationInRenderPass(
device, kRenderTwoTexturesTwoBindgroupsWGSL,
[](wgpu::Device device, wgpu::Texture texture, wgpu::RenderPipeline pipeline,
std::function<void(wgpu::RenderPassEncoder pass)> drawFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
// Bindgroup with different views of same texture
wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});
// Bindgroup with same views of texture
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, 4, 1);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo);
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup0);
pass.SetBindGroup(1, bindGroup1);
pass.End();
// No Error is expected because draw was never called
encoder.Finish();
});
}
constexpr const char* kComputeTwoTexturesOneBindgroupWGSL = R"(
@group(0) @binding(0) var tex0 : texture_2d<f32>;
@group(0) @binding(1) var tex1 : texture_2d<f32>;
@compute @workgroup_size(1)
fn cs() {
_ = tex0;
_ = tex1;
}
)";
constexpr const char* kComputeTwoTexturesTwoBindgroupsWGSL = R"(
@group(0) @binding(0) var tex0 : texture_2d<f32>;
@group(1) @binding(0) var tex1 : texture_2d<f32>;
@compute @workgroup_size(1)
fn cs() {
_ = tex0;
_ = tex1;
}
)";
void TestMultipleTextureViewValidationInComputePass(
wgpu::Device device,
const char* wgsl,
wgpu::TextureUsage textureUsage,
std::function<void(wgpu::Device device,
wgpu::Texture texture,
wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn)> fn) {
wgpu::TextureDescriptor descriptor;
descriptor.size = {2, 1, 1};
descriptor.mipLevelCount = 2;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
descriptor.usage = textureUsage;
wgpu::Texture texture = device.CreateTexture(&descriptor);
constexpr float indirectData[] = {1, 1, 1};
wgpu::Buffer indirectBuffer = utils::CreateBufferFromData(
device, indirectData, sizeof indirectData, wgpu::BufferUsage::Indirect);
// Create a pipeline that will sample from 2 2D textures and output to an attachment.
wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl);
wgpu::ComputePipelineDescriptor pDesc;
pDesc.compute.module = module;
wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pDesc);
fn(device, texture, pipeline,
[](wgpu::ComputePassEncoder pass) { pass.DispatchWorkgroups(1); });
fn(device, texture, pipeline, [indirectBuffer](wgpu::ComputePassEncoder pass) {
pass.DispatchWorkgroupsIndirect(indirectBuffer, 0);
});
indirectBuffer.Destroy();
texture.Destroy();
}
// Test we get a validation error if we have 2 different views of a texture
// in the same bind group. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests, CanNotComputeWithDifferentMipsSameTextureSameBindGroup) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoTexturesOneBindgroupWGSL, wgpu::TextureUsage::TextureBinding,
[this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
wgpu::BindGroup bindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
dispatchFn(pass);
pass.End();
ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
testing::HasSubstr("different views"));
});
}
// Test we get a validation error if we have 2 different views of a texture spanning
// different bind groups. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests,
CanNotComputeWithDifferentMipsSameTextureDifferentBindGroups) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding,
[this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup0);
pass.SetBindGroup(1, bindGroup1);
dispatchFn(pass);
pass.End();
ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
testing::HasSubstr("different views"));
});
}
// Test that it's possible to set a bindgroup that uses a texture with multiple views
// which would be an error if you issued a draw command but, you then fix the issue by replacing
// the bindgroup with one that does not have multiple views. We're trying to test
// that the implementation does the validation at draw command time and not before.
TEST_P(CompatTextureViewValidationTests,
CanBindDifferentMipsSameTextureSameBindGroupAndFixWithoutErrorInComputePass) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoTexturesOneBindgroupWGSL, wgpu::TextureUsage::TextureBinding,
[](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
// Bindgroup with different views of same texture
wgpu::BindGroup badBindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});
// Bindgroup with same views of texture
wgpu::BindGroup goodBindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip0ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, badBindGroup);
pass.SetBindGroup(0, goodBindGroup);
dispatchFn(pass);
pass.End();
// No Error is expected
encoder.Finish();
});
}
// Test that having 2 texture views that have the same settings, in 2 different
// bindgroups, does not generate a validation error.
TEST_P(CompatTextureViewValidationTests, CanBindSameViewIn2BindGroupsInComputePass) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding,
[](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});
// Bindgroup with same view of texture as bindGroup0
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup0);
pass.SetBindGroup(1, bindGroup1);
dispatchFn(pass);
pass.End();
// No Error is expected
encoder.Finish();
});
}
// Test that no validation error happens if we have multiple views of a texture
// but don't draw.
TEST_P(CompatTextureViewValidationTests,
NoErrorIfMultipleDifferentViewsOfTextureAreNotUsedInComputePass) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::TextureBinding,
[](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
// Bindgroup with different views of same texture
wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});
// Bindgroup with same views of texture
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup0);
pass.SetBindGroup(1, bindGroup1);
pass.End();
// No Error is expected because draw was never called
encoder.Finish();
});
}
constexpr const char* kComputeTwoStorageTexturesOneBindgroupWGSL = R"(
@group(0) @binding(0) var tex0 : texture_storage_2d<rgba8unorm, write>;
@group(0) @binding(1) var tex1 : texture_storage_2d<rgba8unorm, write>;
@compute @workgroup_size(1)
fn cs() {
_ = tex0;
_ = tex1;
}
)";
constexpr const char* kComputeTwoStorageTexturesTwoBindgroupsWGSL = R"(
@group(0) @binding(0) var tex0 : texture_storage_2d<rgba8unorm, write>;
@group(1) @binding(0) var tex1 : texture_storage_2d<rgba8unorm, write>;
@compute @workgroup_size(1)
fn cs() {
_ = tex0;
_ = tex1;
}
)";
// Test we get a validation error if we have 2 different views of a storage texture
// in the same bind group. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests,
CanNotComputeWithDifferentMipsSameStorageTextureSameBindGroup) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoStorageTexturesOneBindgroupWGSL, wgpu::TextureUsage::StorageBinding,
[this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
wgpu::BindGroup bindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup);
dispatchFn(pass);
pass.End();
ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
testing::HasSubstr("different views"));
});
}
// Test we get a validation error if we have 2 different views of a texture spanning
// different bind groups. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewValidationTests,
CanNotComputeWithDifferentMipsSameStorageTextureDifferentBindGroups) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoStorageTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::StorageBinding,
[this](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup0);
pass.SetBindGroup(1, bindGroup1);
dispatchFn(pass);
pass.End();
ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(encoder.Finish(),
testing::HasSubstr("different views"));
});
}
// Test that it's possible to set a bindgroup that uses a texture with multiple views
// which would be an error if you issued a draw command but, you then fix the issue by replacing
// the bindgroup with one that does not have multiple views. We're trying to test
// that the implementation does the validation at draw command time and not before.
TEST_P(CompatTextureViewValidationTests,
CanBindDifferentMipsSameStorageTextureSameBindGroupAndFixWithoutErrorInComputePass) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoStorageTexturesOneBindgroupWGSL, wgpu::TextureUsage::StorageBinding,
[](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.baseMipLevel = 0;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
// Bindgroup with different views of same texture
wgpu::BindGroup badBindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture.CreateView(&mip1ViewDesc)}});
wgpu::TextureDescriptor descriptor;
descriptor.size = {2, 1, 1};
descriptor.mipLevelCount = 2;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
descriptor.usage = wgpu::TextureUsage::StorageBinding;
wgpu::Texture texture2 = device.CreateTexture(&descriptor);
// Bindgroup with same views of texture
wgpu::BindGroup goodBindGroup = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0),
{{0, texture.CreateView(&mip0ViewDesc)}, {1, texture2.CreateView(&mip0ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, badBindGroup);
pass.SetBindGroup(0, goodBindGroup);
dispatchFn(pass);
pass.End();
// No Error is expected
encoder.Finish();
});
}
// Test that no validation error happens if we have multiple views of a texture
// but don't draw.
TEST_P(CompatTextureViewValidationTests,
NoErrorIfMultipleDifferentViewsOfStorageTextureAreNotUsedInComputePass) {
TestMultipleTextureViewValidationInComputePass(
device, kComputeTwoStorageTexturesTwoBindgroupsWGSL, wgpu::TextureUsage::StorageBinding,
[](wgpu::Device device, wgpu::Texture texture, wgpu::ComputePipeline pipeline,
std::function<void(wgpu::ComputePassEncoder pass)> dispatchFn) {
wgpu::TextureViewDescriptor mip0ViewDesc;
mip0ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip0ViewDesc.mipLevelCount = 1;
wgpu::TextureViewDescriptor mip1ViewDesc;
mip1ViewDesc.dimension = wgpu::TextureViewDimension::e2D;
mip1ViewDesc.baseMipLevel = 1;
mip1ViewDesc.mipLevelCount = 1;
// Bindgroup with different views of same texture
wgpu::BindGroup bindGroup0 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(0), {{0, texture.CreateView(&mip0ViewDesc)}});
// Bindgroup with same views of texture
wgpu::BindGroup bindGroup1 = utils::MakeBindGroup(
device, pipeline.GetBindGroupLayout(1), {{0, texture.CreateView(&mip1ViewDesc)}});
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePassEncoder pass = encoder.BeginComputePass({});
pass.SetPipeline(pipeline);
pass.SetBindGroup(0, bindGroup0);
pass.SetBindGroup(1, bindGroup1);
pass.End();
// No Error is expected because draw was never called
encoder.Finish();
});
}
TEST_F(CompatValidationTest, CanNotCreateBGRA8UnormSRGBTexture) {
wgpu::TextureDescriptor descriptor;
descriptor.size = {1, 1, 1};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::BGRA8UnormSrgb;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor),
testing::HasSubstr("not supported in compatibility mode"));
}
TEST_F(CompatValidationTest, CanNotCreateBGRA8UnormTextureWithBGRA8UnormSrgbView) {
constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::BGRA8UnormSrgb;
wgpu::TextureDescriptor descriptor;
descriptor.size = {1, 1, 1};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = wgpu::TextureFormat::BGRA8Unorm;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
descriptor.viewFormatCount = 1;
descriptor.viewFormats = &viewFormat;
ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor),
testing::HasSubstr("not supported in compatibility mode"));
}
TEST_F(CompatValidationTest, CanNotCopyMultisampleTextureToTexture) {
wgpu::TextureDescriptor srcDescriptor;
srcDescriptor.size = {4, 4, 1};
srcDescriptor.dimension = wgpu::TextureDimension::e2D;
srcDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
srcDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
srcDescriptor.sampleCount = 4;
wgpu::Texture srcTexture = device.CreateTexture(&srcDescriptor);
wgpu::TextureDescriptor dstDescriptor;
dstDescriptor.size = {4, 4, 1};
dstDescriptor.dimension = wgpu::TextureDimension::e2D;
dstDescriptor.format = wgpu::TextureFormat::RGBA8Unorm;
dstDescriptor.usage = wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment;
dstDescriptor.sampleCount = 4;
wgpu::Texture dstTexture = device.CreateTexture(&dstDescriptor);
wgpu::TexelCopyTextureInfo source = utils::CreateTexelCopyTextureInfo(srcTexture);
wgpu::TexelCopyTextureInfo destination = utils::CreateTexelCopyTextureInfo(dstTexture);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToTexture(&source, &destination, &srcDescriptor.size);
ASSERT_DEVICE_ERROR(encoder.Finish(),
testing::HasSubstr("cannot be copied in compatibility mode"));
}
// Regression test for crbug.com/339704108
// Error texture should not resolve mCompatibilityTextureBindingViewDimension,
// as dimension could be in bad form.
TEST_P(CompatTextureViewValidationTests,
DoNotResolveDefaultTextureBindingViewDimensionOnErrorTexture) {
// Set incompatible texture format and view format.
// This validation happens before texture dimension validation and binding view dimension
// resolving and shall return an error texture.
constexpr wgpu::TextureFormat format = wgpu::TextureFormat::BGRA8Unorm;
constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8UnormSrgb;
wgpu::TextureDescriptor descriptor;
descriptor.size = {1, 1, 1};
descriptor.dimension = wgpu::TextureDimension::Undefined;
descriptor.format = format;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
descriptor.viewFormatCount = 1;
descriptor.viewFormats = &viewFormat;
ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
}
// Regression test for crbug.com/341167195
// Resolved default compatibility textureBindingViewDimension should be validated as it may come
// from the TextureBindingViewDimensionDescriptor
TEST_P(CompatTextureViewValidationTests, InvalidTextureBindingViewDimensionDescriptorDescriptor) {
wgpu::TextureDescriptor descriptor;
descriptor.size = {1, 1, 1};
descriptor.dimension = wgpu::TextureDimension::Undefined;
descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
descriptor.usage = wgpu::TextureUsage::TextureBinding;
wgpu::TextureBindingViewDimensionDescriptor textureBindingViewDimensionDesc;
descriptor.nextInChain = &textureBindingViewDimensionDesc;
// Forcefully set an invalid view dimension.
textureBindingViewDimensionDesc.textureBindingViewDimension =
static_cast<wgpu::TextureViewDimension>(99);
ASSERT_TEXTURE_VIEW_ERROR_IF_NO_FLEXIBLE_FEATURE(device.CreateTexture(&descriptor), testing::_);
}
class CompatTextureViewDimensionValidationTests : public CompatTextureViewValidationTests {
protected:
void TestBindingTextureViewDimensions(
const uint32_t depth,
const wgpu::TextureViewDimension textureBindingViewDimension,
const wgpu::TextureViewDimension viewDimension,
bool success) {
wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout(
device, {{0, wgpu::ShaderStage::Fragment, wgpu::TextureSampleType::Float,
viewDimension == wgpu::TextureViewDimension::Undefined
? wgpu::TextureViewDimension::e2D
: viewDimension}});
wgpu::Texture texture = CreateTextureWithViewDimension(depth, wgpu::TextureDimension::e2D,
textureBindingViewDimension);
wgpu::TextureViewDescriptor viewDesc = {};
viewDesc.dimension = viewDimension;
if (success) {
utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}});
} else {
ASSERT_DEVICE_ERROR(
utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}}),
testing::HasSubstr("must match textureBindingViewDimension"));
}
texture.Destroy();
}
void TestCreateTextureWithViewDimensionImpl(
const uint32_t depth,
const wgpu::TextureDimension dimension,
const wgpu::TextureViewDimension textureBindingViewDimension,
bool success,
const char* expectedSubstr) {
if (success) {
CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension);
} else {
ASSERT_DEVICE_ERROR(
CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension);
testing::HasSubstr(expectedSubstr));
}
}
void TestCreateTextureIsCompatibleWithViewDimension(
const uint32_t depth,
const wgpu::TextureDimension dimension,
const wgpu::TextureViewDimension textureBindingViewDimension,
bool success) {
TestCreateTextureWithViewDimensionImpl(depth, dimension, textureBindingViewDimension,
success, "is not compatible with the dimension");
}
void TestCreateTextureLayersIsCompatibleWithViewDimension(
const uint32_t depth,
const wgpu::TextureDimension dimension,
const wgpu::TextureViewDimension textureBindingViewDimension,
bool success) {
TestCreateTextureWithViewDimensionImpl(depth, dimension, textureBindingViewDimension,
success,
"is only compatible with depthOrArrayLayers ==");
}
wgpu::Texture CreateTextureWithViewDimension(
const uint32_t depth,
const wgpu::TextureDimension dimension,
const wgpu::TextureViewDimension textureBindingViewDimension) {
constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8Unorm;
wgpu::TextureDescriptor textureDesc;
textureDesc.size = {1, 1, depth};
textureDesc.dimension = dimension;
textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
textureDesc.usage = wgpu::TextureUsage::TextureBinding;
textureDesc.viewFormatCount = 1;
textureDesc.viewFormats = &viewFormat;
wgpu::TextureBindingViewDimensionDescriptor textureBindingViewDimensionDesc;
if (textureBindingViewDimension != wgpu::TextureViewDimension::Undefined) {
textureDesc.nextInChain = &textureBindingViewDimensionDesc;
textureBindingViewDimensionDesc.textureBindingViewDimension =
textureBindingViewDimension;
}
return device.CreateTexture(&textureDesc);
}
};
// Note: CubeArray is not included because CubeArray is not allowed
// in compatibility mode.
const wgpu::TextureViewDimension kViewDimensions[] = {
wgpu::TextureViewDimension::e1D, wgpu::TextureViewDimension::e2D,
wgpu::TextureViewDimension::e3D, wgpu::TextureViewDimension::e2DArray,
wgpu::TextureViewDimension::Cube,
};
// Test creating 1d textures with each view dimension. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, E1D) {
for (auto viewDimension : kViewDimensions) {
TestCreateTextureIsCompatibleWithViewDimension(
1, wgpu::TextureDimension::e1D, viewDimension,
HasFlexibleTextureViews() || viewDimension == wgpu::TextureViewDimension::e1D);
}
}
// Test creating 2d textures with each view dimension. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, E2D) {
for (auto viewDimension : kViewDimensions) {
TestCreateTextureIsCompatibleWithViewDimension(
viewDimension == wgpu::TextureViewDimension::e2D ? 1 : 6, wgpu::TextureDimension::e2D,
viewDimension,
HasFlexibleTextureViews() || (viewDimension != wgpu::TextureViewDimension::e1D &&
viewDimension != wgpu::TextureViewDimension::e3D));
}
}
// Test creating 1d textures with each view dimension. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, E3D) {
for (auto viewDimension : kViewDimensions) {
TestCreateTextureIsCompatibleWithViewDimension(
1, wgpu::TextureDimension::e3D, viewDimension,
HasFlexibleTextureViews() || viewDimension == wgpu::TextureViewDimension::e3D);
}
}
// Test creating a 2d texture with a 2d view and depthOrArrayLayers > 1 fails. Unless
// FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, E2DViewMoreThan1Layer) {
TestCreateTextureLayersIsCompatibleWithViewDimension(
2, wgpu::TextureDimension::e2D, wgpu::TextureViewDimension::e2D, HasFlexibleTextureViews());
}
// Test creating a 2d texture with a cube view with depthOrArrayLayers != 6 fails. Unless
// FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, CubeViewMoreWhereLayersIsNot6) {
uint32_t layers[] = {1, 5, 6, 7, 12};
for (auto numLayers : layers) {
TestCreateTextureLayersIsCompatibleWithViewDimension(
numLayers, wgpu::TextureDimension::e2D, wgpu::TextureViewDimension::Cube,
HasFlexibleTextureViews() || numLayers == 6);
}
}
TEST_P(CompatTextureViewDimensionValidationTests, OneLayerIs2DView) {
TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::e2D, true);
}
// Test 2 layer texture gets a 2d-array viewDimension
TEST_P(CompatTextureViewDimensionValidationTests, TwoLayersIs2DArrayView) {
TestBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::e2DArray, true);
}
// Test 6 layer texture gets a 2d-array viewDimension
TEST_P(CompatTextureViewDimensionValidationTests, SixLayersIs2DArrayView) {
TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined,
wgpu::TextureViewDimension::e2DArray, true);
}
// Test 2d texture can not be viewed as 2D array. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, TwoDTextureViewDimensionCanNotBeViewedAs2DArray) {
TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::e2D,
wgpu::TextureViewDimension::e2DArray,
HasFlexibleTextureViews());
}
// Test 2d-array texture can not be viewed as cube. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests,
TwoDArrayTextureViewDimensionCanNotBeViewedAsCube) {
TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::e2DArray,
wgpu::TextureViewDimension::Cube, HasFlexibleTextureViews());
}
// Test cube texture can not be viewed as 2d-array. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, CubeTextureViewDimensionCanNotBeViewedAs2DArray) {
TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Cube,
wgpu::TextureViewDimension::e2DArray,
HasFlexibleTextureViews());
}
// Test 2Darray != 2d
// Test cube !== 2d
// Test cube !== 2d-array
class CompatCompressedCopyT2BAndCopyT2TValidationTests : public CompatValidationTest {
protected:
std::vector<wgpu::FeatureName> GetRequiredFeatures() override {
std::vector<wgpu::FeatureName> requiredFeatures;
for (TextureInfo textureInfo : textureInfos) {
if (adapter.HasFeature(textureInfo.feature)) {
requiredFeatures.push_back(textureInfo.feature);
}
}
return requiredFeatures;
}
struct TextureInfo {
wgpu::FeatureName feature;
wgpu::TextureFormat format;
};
static constexpr TextureInfo textureInfos[] = {
{
wgpu::FeatureName::TextureCompressionBC,
wgpu::TextureFormat::BC2RGBAUnorm,
},
{
wgpu::FeatureName::TextureCompressionETC2,
wgpu::TextureFormat::ETC2RGB8Unorm,
},
{
wgpu::FeatureName::TextureCompressionASTC,
wgpu::TextureFormat::ASTC4x4Unorm,
},
};
};
TEST_F(CompatCompressedCopyT2BAndCopyT2TValidationTests, CanNotCopyCompressedTextureToBuffer) {
for (TextureInfo textureInfo : textureInfos) {
if (!device.HasFeature(textureInfo.feature)) {
continue;
}
wgpu::TextureDescriptor descriptor;
descriptor.size = {4, 4, 1};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = textureInfo.format;
descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc;
wgpu::Texture texture = device.CreateTexture(&descriptor);
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = 256 * 4;
bufferDescriptor.usage = wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
wgpu::TexelCopyTextureInfo source = utils::CreateTexelCopyTextureInfo(texture);
wgpu::TexelCopyBufferInfo destination = utils::CreateTexelCopyBufferInfo(buffer, 0, 256, 4);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToBuffer(&source, &destination, &descriptor.size);
ASSERT_DEVICE_ERROR(encoder.Finish(), testing::HasSubstr("cannot be used"));
}
}
TEST_F(CompatCompressedCopyT2BAndCopyT2TValidationTests, CanNotCopyCompressedTextureToTexture) {
for (TextureInfo textureInfo : textureInfos) {
if (!device.HasFeature(textureInfo.feature)) {
continue;
}
wgpu::TextureDescriptor descriptor;
descriptor.size = {4, 4, 1};
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.format = textureInfo.format;
descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc;
wgpu::Texture srcTexture = device.CreateTexture(&descriptor);
descriptor.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst;
wgpu::Texture dstTexture = device.CreateTexture(&descriptor);
wgpu::TexelCopyTextureInfo source = utils::CreateTexelCopyTextureInfo(srcTexture);
wgpu::TexelCopyTextureInfo destination = utils::CreateTexelCopyTextureInfo(dstTexture);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
encoder.CopyTextureToTexture(&source, &destination, &descriptor.size);
ASSERT_DEVICE_ERROR(encoder.Finish(), testing::HasSubstr("cannot be used"));
}
}
class CompatMaxVertexAttributesTest : public CompatValidationTest {
protected:
void TestMaxVertexAttributes(bool usesVertexIndex, bool usesInstanceIndex) {
wgpu::Limits limits;
device.GetLimits(&limits);
uint32_t maxAttributes = limits.maxVertexAttributes;
uint32_t numAttributesUsedByBuiltins =
(usesVertexIndex ? 1 : 0) + (usesInstanceIndex ? 1 : 0);
TestAttributes(maxAttributes - numAttributesUsedByBuiltins, usesVertexIndex,
usesInstanceIndex, true);
if (usesVertexIndex || usesInstanceIndex) {
TestAttributes(maxAttributes - numAttributesUsedByBuiltins + 1, usesVertexIndex,
usesInstanceIndex, false);
}
}
void TestAttributes(uint32_t numAttributes,
bool usesVertexIndex,
bool usesInstanceIndex,
bool expectSuccess) {
std::vector<std::string> inputs;
std::vector<std::string> outputs;
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.layout = {};
descriptor.vertex.bufferCount = 1;
descriptor.cBuffers[0].arrayStride = 16;
descriptor.cBuffers[0].attributeCount = numAttributes;
for (uint32_t i = 0; i < numAttributes; ++i) {
inputs.push_back(absl::StrFormat("@location(%u) v%u: vec4f", i, i));
outputs.push_back(absl::StrFormat("v%u", i));
descriptor.cAttributes[i].format = wgpu::VertexFormat::Float32x4;
descriptor.cAttributes[i].shaderLocation = i;
}
if (usesVertexIndex) {
inputs.push_back("@builtin(vertex_index) vNdx: u32");
outputs.push_back("vec4f(f32(vNdx))");
}
if (usesInstanceIndex) {
inputs.push_back("@builtin(instance_index) iNdx: u32");
outputs.push_back("vec4f(f32(iNdx))");
}
auto wgsl = absl::StrFormat(R"(
@fragment fn fs() -> @location(0) vec4f {
return vec4f(1);
}
@vertex fn vs(%s) -> @builtin(position) vec4f {
return %s;
}
)",
absl::StrJoin(inputs, ", "), absl::StrJoin(outputs, " + "));
wgpu::ShaderModule module = utils::CreateShaderModule(device, wgsl.c_str());
descriptor.vertex.module = module;
descriptor.cFragment.module = module;
if (expectSuccess) {
device.CreateRenderPipeline(&descriptor);
} else {
ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor),
testing::HasSubstr("compat"));
}
}
};
TEST_F(CompatMaxVertexAttributesTest, CanUseMaxVertexAttributes) {
TestMaxVertexAttributes(false, false);
}
TEST_F(CompatMaxVertexAttributesTest, VertexIndexTakesAnAttribute) {
TestMaxVertexAttributes(true, false);
}
TEST_F(CompatMaxVertexAttributesTest, InstanceIndexTakesAnAttribute) {
TestMaxVertexAttributes(false, true);
}
TEST_F(CompatMaxVertexAttributesTest, VertexAndInstanceIndexEachTakeAnAttribute) {
TestMaxVertexAttributes(true, true);
}
INSTANTIATE_TEST_SUITE_P(,
CompatTextureViewValidationTests,
::testing::Values(FlexibleTextureViewsFeature::Disabled,
FlexibleTextureViewsFeature::Enabled),
CompatTextureViewValidationTests::PrintToStringParamName);
INSTANTIATE_TEST_SUITE_P(,
CompatTextureViewDimensionValidationTests,
::testing::Values(FlexibleTextureViewsFeature::Disabled,
FlexibleTextureViewsFeature::Enabled),
CompatTextureViewValidationTests::PrintToStringParamName);
class CompatLayoutLimitsTests : public CompatValidationTest {
protected:
wgpu::Limits GetRequiredLimits(const wgpu::Limits& supported) override {
wgpu::Limits required = {};
required.maxStorageBuffersInFragmentStage = supported.maxStorageBuffersInFragmentStage / 2;
required.maxStorageBuffersInVertexStage = supported.maxStorageBuffersInVertexStage / 2;
required.maxStorageTexturesInFragmentStage =
supported.maxStorageTexturesInFragmentStage / 2;
required.maxStorageTexturesInVertexStage = supported.maxStorageTexturesInVertexStage / 2;
required.maxStorageBuffersPerShaderStage = supported.maxStorageBuffersPerShaderStage;
required.maxStorageTexturesPerShaderStage = supported.maxStorageTexturesPerShaderStage;
return required;
}
void DoBindGroupLayoutTest(uint32_t limitInStage,
uint32_t limitPerStage,
const wgpu::BindGroupLayoutEntry& entry,
const char* expectedErrorSubstring) {
EXPECT_TRUE(limitInStage > 0);
EXPECT_TRUE(limitInStage < limitPerStage);
std::vector<wgpu::BindGroupLayoutEntry> entries(limitInStage + 1);
for (size_t i = 0; i < entries.size(); ++i) {
entries[i] = entry;
entries[i].binding = i;
}
wgpu::BindGroupLayoutDescriptor descriptor;
descriptor.entryCount = entries.size();
descriptor.entries = entries.data();
ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor),
testing::HasSubstr(expectedErrorSubstring));
}
void DoPipelineLayoutTest(uint32_t limitInStage,
uint32_t limitPerStage,
const wgpu::BindGroupLayoutEntry& entry,
const char* expectedErrorSubstring) {
EXPECT_TRUE(limitInStage > 0);
EXPECT_TRUE(limitInStage < limitPerStage);
wgpu::BindGroupLayout bgls[2];
std::vector<wgpu::BindGroupLayoutEntry> entries(limitInStage);
for (size_t i = 0; i < entries.size(); ++i) {
entries[i] = entry;
entries[i].binding = i;
}
wgpu::BindGroupLayoutDescriptor descriptor;
descriptor.entryCount = entries.size();
descriptor.entries = entries.data();
bgls[0] = device.CreateBindGroupLayout(&descriptor);
descriptor.entryCount = 1;
descriptor.entries = entries.data();
bgls[1] = device.CreateBindGroupLayout(&descriptor);
wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor = {};
pipelineLayoutDescriptor.bindGroupLayoutCount = 2;
pipelineLayoutDescriptor.bindGroupLayouts = bgls;
ASSERT_DEVICE_ERROR(device.CreatePipelineLayout(&pipelineLayoutDescriptor),
testing::HasSubstr(expectedErrorSubstring));
}
};
// Test that in compat we get an error if we use more than maxStorageBuffersInFragmentStage
// when it's lower than maxStorageBuffersPerShaderStage in createBindGroupLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageBuffersInFragmentStageBindGroupLayout) {
const auto limits = GetSupportedLimits();
wgpu::BindGroupLayoutEntry entry;
entry.visibility = wgpu::ShaderStage::Fragment;
entry.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
DoBindGroupLayoutTest(limits.maxStorageBuffersInFragmentStage,
limits.maxStorageBuffersPerShaderStage, entry,
"maxStorageBuffersInFragmentStage");
}
// Test that in compat we get an error if we use more than maxStorageBuffersInVertexStage
// when it's lower than maxStorageBuffersPerShaderStage in createBindGroupLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageBuffersInVertexStageBindGroupLayout) {
const auto limits = GetSupportedLimits();
wgpu::BindGroupLayoutEntry entry;
entry.visibility = wgpu::ShaderStage::Vertex;
entry.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
DoBindGroupLayoutTest(limits.maxStorageBuffersInVertexStage,
limits.maxStorageBuffersPerShaderStage, entry,
"maxStorageBuffersInVertexStage");
}
// Test that in compat we get an error if we use more than maxStorageTexturesInVertexStage
// when it's lower than maxStorageTexturesPerShaderStage in createBindGroupLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageTexturesInVertexStageBindGroupLayout) {
const auto limits = GetSupportedLimits();
wgpu::BindGroupLayoutEntry entry;
entry.visibility = wgpu::ShaderStage::Vertex;
entry.storageTexture.format = wgpu::TextureFormat::R32Float;
entry.storageTexture.access = wgpu::StorageTextureAccess::ReadOnly;
DoBindGroupLayoutTest(limits.maxStorageTexturesInVertexStage,
limits.maxStorageTexturesPerShaderStage, entry,
"maxStorageTexturesInVertexStage");
}
// Test that in compat we get an error if we use more than maxStorageBuffersInFragmentStage
// when it's lower than maxStorageBuffersPerShaderStage in createPipelineLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageBuffersInFragmentStagePipelineLayout) {
const auto limits = GetSupportedLimits();
wgpu::BindGroupLayoutEntry entry;
entry.visibility = wgpu::ShaderStage::Fragment;
entry.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
DoPipelineLayoutTest(limits.maxStorageBuffersInFragmentStage,
limits.maxStorageBuffersPerShaderStage, entry,
"maxStorageBuffersInFragmentStage");
}
// Test that in compat we get an error if we use more than maxStorageBuffersInVertexStage
// when it's lower than maxStorageBuffersPerShaderStage in createPipelineLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageBuffersInVertexStagePipelineLayout) {
const auto limits = GetSupportedLimits();
wgpu::BindGroupLayoutEntry entry;
entry.visibility = wgpu::ShaderStage::Vertex;
entry.buffer.type = wgpu::BufferBindingType::ReadOnlyStorage;
DoPipelineLayoutTest(limits.maxStorageBuffersInVertexStage,
limits.maxStorageBuffersPerShaderStage, entry,
"maxStorageBuffersInVertexStage");
}
// Test that in compat we get an error if we use more than maxStorageTexturesInVertexStage
// when it's lower than maxStorageTexturesPerShaderStage in createPipelineLayout
TEST_F(CompatLayoutLimitsTests, CanNotPassLimitOfStorageTexturesInVertexStagePipelineLayout) {
const auto limits = GetSupportedLimits();
wgpu::BindGroupLayoutEntry entry;
entry.visibility = wgpu::ShaderStage::Vertex;
entry.storageTexture.format = wgpu::TextureFormat::R32Float;
entry.storageTexture.access = wgpu::StorageTextureAccess::ReadOnly;
DoPipelineLayoutTest(limits.maxStorageTexturesInVertexStage,
limits.maxStorageTexturesPerShaderStage, entry,
"maxStorageTexturesInVertexStage");
}
} // anonymous namespace
} // namespace dawn