export const description = `Validation tests for uniformity analysis`;
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { keysOf } from '../../../../common/util/data_tables.js';
import { unreachable } from '../../../../common/util/util.js';
import { WGSLLanguageFeature } from '../../../capability_info.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
import { Snippet, LoopCase, compileShouldSucceed } from './snippet.js';
export const g = makeTestGroup(ShaderValidationTest);
const kCollectiveOps = [
{ op: 'control_case_compute', stage: 'compute' },
{ op: 'control_case_fragment', stage: 'fragment' },
{ op: 'textureSample', stage: 'fragment' },
{ op: 'textureSampleBias', stage: 'fragment' },
{ op: 'textureSampleCompare', stage: 'fragment' },
{ op: 'dpdx', stage: 'fragment' },
{ op: 'dpdxCoarse', stage: 'fragment' },
{ op: 'dpdxFine', stage: 'fragment' },
{ op: 'dpdy', stage: 'fragment' },
{ op: 'dpdyCoarse', stage: 'fragment' },
{ op: 'dpdyFine', stage: 'fragment' },
{ op: 'fwidth', stage: 'fragment' },
{ op: 'fwidthCoarse', stage: 'fragment' },
{ op: 'fwidthFine', stage: 'fragment' },
{ op: 'storageBarrier', stage: 'compute' },
{ op: 'textureBarrier', stage: 'compute' },
{ op: 'workgroupBarrier', stage: 'compute' },
{ op: 'workgroupUniformLoad', stage: 'compute' },
];
const kConditions = [
{ cond: 'uniform_storage_ro', expectation: true },
{ cond: 'nonuniform_storage_ro', expectation: false },
{ cond: 'nonuniform_storage_rw', expectation: false },
{ cond: 'nonuniform_builtin', expectation: false },
{ cond: 'uniform_literal', expectation: true },
{ cond: 'uniform_const', expectation: true },
{ cond: 'uniform_override', expectation: true },
{ cond: 'uniform_let', expectation: true },
{ cond: 'nonuniform_let', expectation: false },
{ cond: 'uniform_or', expectation: true },
{ cond: 'nonuniform_or1', expectation: false },
{ cond: 'nonuniform_or2', expectation: false },
{ cond: 'uniform_and', expectation: true },
{ cond: 'nonuniform_and1', expectation: false },
{ cond: 'nonuniform_and2', expectation: false },
{ cond: 'uniform_func_var', expectation: true },
{ cond: 'nonuniform_func_var', expectation: false },
{ cond: 'storage_texture_ro', expectation: true },
{ cond: 'storage_texture_rw', expectation: false },
];
function generateCondition(condition: string): string {
switch (condition) {
case 'uniform_storage_ro': {
return `ro_buffer[0] == 0`;
}
case 'nonuniform_storage_ro': {
return `ro_buffer[priv_var[0]] == 0`;
}
case 'nonuniform_storage_rw': {
return `rw_buffer[0] == 0`;
}
case 'nonuniform_builtin': {
return `p.x == 0`;
}
case 'uniform_literal': {
return `false`;
}
case 'uniform_const': {
return `c`;
}
case 'uniform_override': {
return `o == 0`;
}
case 'uniform_let': {
return `u_let == 0`;
}
case 'nonuniform_let': {
return `n_let == 0`;
}
case 'uniform_or': {
return `u_let == 0 || uniform_buffer.y > 1`;
}
case 'nonuniform_or1': {
return `u_let == 0 || n_let == 0`;
}
case 'nonuniform_or2': {
return `n_let == 0 || u_let == 0`;
}
case 'uniform_and': {
return `u_let == 0 && uniform_buffer.y > 1`;
}
case 'nonuniform_and1': {
return `u_let == 0 && n_let == 0`;
}
case 'nonuniform_and2': {
return `n_let == 0 && u_let == 0`;
}
case 'uniform_func_var': {
return `u_f == 0`;
}
case 'nonuniform_func_var': {
return `n_f == 0`;
}
case 'storage_texture_ro': {
return `textureLoad(ro_storage_texture, vec2()).x == 0`;
}
case 'storage_texture_rw': {
return `textureLoad(rw_storage_texture, vec2()).x == 0`;
}
case 'control_case': {
return 'true';
}
case 'subgroupAdd':
case 'subgroupInclusiveAdd':
case 'subgroupExclusiveAdd':
case 'subgroupMul':
case 'subgroupInclusiveMul':
case 'subgroupExclusiveMul':
case 'subgroupMax':
case 'subgroupMin':
case 'subgroupAnd':
case 'subgroupOr':
case 'subgroupXor':
case 'subgroupBroadcastFirst':
case 'quadSwapX':
case 'quadSwapY':
case 'quadSwapDiagonal': {
return `${condition}(0) == 0`;
}
case 'subgroupAll':
case 'subgroupAny': {
return `${condition}(false)`;
}
case 'subgroupBallot': {
return `${condition}(false).x == 0`;
}
case 'subgroupElect': {
return `${condition}()`;
}
case 'subgroupBroadcast':
case 'subgroupShuffle':
case 'subgroupShuffleUp':
case 'subgroupShuffleDown':
case 'subgroupShuffleXor':
case 'quadBroadcast': {
return `${condition}(0, 0) == 0`;
}
default: {
unreachable(`Unhandled condition`);
}
}
}
function generateOp(op: string): string {
switch (op) {
case 'control_case':
case 'control_case_compute':
case 'control_case_fragment': {
return ``;
}
case 'textureSample': {
return `let x = ${op}(tex, s, vec2(0,0));\n`;
}
case 'textureSampleBias': {
return `let x = ${op}(tex, s, vec2(0,0), 0);\n`;
}
case 'textureSampleCompare': {
return `let x = ${op}(tex_depth, s_comp, vec2(0,0), 0);\n`;
}
case 'storageBarrier':
case 'textureBarrier':
case 'workgroupBarrier': {
return `${op}();\n`;
}
case 'workgroupUniformLoad': {
return `let x = ${op}(&wg);`;
}
case 'dpdx':
case 'dpdxCoarse':
case 'dpdxFine':
case 'dpdy':
case 'dpdyCoarse':
case 'dpdyFine':
case 'fwidth':
case 'fwidthCoarse':
case 'fwidthFine': {
return `let x = ${op}(0);\n`;
}
case 'subgroupAdd':
case 'subgroupInclusiveAdd':
case 'subgroupExclusiveAdd':
case 'subgroupMul':
case 'subgroupInclusiveMul':
case 'subgroupExclusiveMul':
case 'subgroupMax':
case 'subgroupMin':
case 'subgroupAnd':
case 'subgroupOr':
case 'subgroupXor':
case 'subgroupBroadcastFirst':
case 'quadSwapX':
case 'quadSwapY':
case 'quadSwapDiagonal':
return `let x = ${op}(0);\n`;
case 'subgroupAll':
case 'subgroupAny':
case 'subgroupBallot':
return `let x = ${op}(false);\n`;
case 'subgroupElect':
return `let x = ${op}();\n`;
case 'subgroupBroadcast':
case 'subgroupShuffle':
case 'subgroupShuffleUp':
case 'subgroupShuffleDown':
case 'subgroupShuffleXor':
case 'quadBroadcast':
return `let x = ${op}(0, 0);\n`;
default: {
unreachable(`Unhandled op`);
}
}
}
const kStatementCases = [
{
name: 'if',
code: 'if <cond> { <op> }',
verdict: 'sensitive',
},
{
name: 'switch',
code: `
switch u32(<cond>) {
case 0: {
<op>
}
default: { }
}`,
verdict: 'sensitive',
},
LoopCase('loop-op-always-break', 'permit'),
LoopCase('loop-op-cond-break', 'sensitive'),
LoopCase('loop-op-always-return', 'permit'),
LoopCase('loop-op-cond-return', 'sensitive'),
LoopCase('loop-unif-break-op-always-continue', 'permit'),
LoopCase('loop-unif-break-op-cond-continue', 'sensitive'),
LoopCase('loop-always-break-op', 'permit'),
LoopCase('loop-cond-break-op', 'sensitive'),
LoopCase('loop-always-return-op', 'permit'),
LoopCase('loop-cond-return-op', 'sensitive'),
LoopCase('loop-unif-break-always-continue-op', 'permit'),
LoopCase('loop-unif-break-cond-continue-op', 'sensitive'),
LoopCase('loop-always-break-end-op', 'permit'),
LoopCase('loop-unif-break-end-op', 'permit'),
LoopCase('loop-cond-break-end-op', 'permit'),
LoopCase('loop-always-return-end-op', 'permit'),
LoopCase('loop-cond-return-end-op', 'permit'),
LoopCase('loop-unif-break-always-continue-end-op', 'permit'),
LoopCase('loop-unif-break-cond-continue-end-op', 'permit'),
LoopCase('loop-op-always-break-continuing', 'permit'),
LoopCase('loop-op-unif-break-continuing', 'permit'),
LoopCase('loop-op-cond-break-continuing', 'sensitive'),
LoopCase('loop-op-always-return-continuing', 'permit'),
LoopCase('loop-op-cond-return-continuing', 'sensitive'),
LoopCase('loop-unif-break-op-always-continue-continuing', 'permit'),
LoopCase('loop-unif-break-op-cond-continue-continuing', 'sensitive'),
LoopCase('loop-op-continuing-cond-break', 'sensitive'),
LoopCase('loop-always-break-continuing-op', 'permit'),
LoopCase('loop-cond-break-continuing-op', 'sensitive'),
LoopCase('loop-always-return-continuing-op', 'permit'),
LoopCase('loop-cond-return-continuing-op', 'sensitive'),
LoopCase('loop-continuing-op-cond-break', 'sensitive'),
LoopCase('loop-always-break-continuing-end-op', 'permit'),
LoopCase('loop-cond-break-continuing-end-op', 'permit'),
LoopCase('loop-always-return-continuing-end-op', 'permit'),
LoopCase('loop-cond-return-continuing-end-op', 'permit'),
LoopCase('loop-unif-break-always-continue-continuing-end-op', 'permit'),
LoopCase('loop-unif-break-cond-continue-continuing-end-op', 'permit'),
LoopCase('loop-continuing-cond-break-end-op', 'permit'),
LoopCase('loop-always-break-continuing-cond-break-end-op', 'permit'),
LoopCase('loop-always-return-continuing-cond-break-end-op', 'permit'),
LoopCase('for-always-break-op', 'permit'),
LoopCase('for-cond-break-op', 'sensitive'),
LoopCase('for-always-return-op', 'permit'),
LoopCase('for-cond-return-op', 'sensitive'),
LoopCase('for-unif-unif-break-always-continue-op', 'permit'),
LoopCase('for-unif-unif-break-cond-continue-op', 'sensitive'),
LoopCase('for-op-always-break', 'permit'),
LoopCase('for-op-cond-break', 'sensitive'),
LoopCase('for-op-always-return', 'permit'),
LoopCase('for-op-cond-return', 'sensitive'),
LoopCase('for-op-unif-break-always-continue', 'permit'),
LoopCase('for-op-unif-break-cond-continue', 'sensitive'),
LoopCase('for-unif-op', 'permit'),
LoopCase('for-unif-always-break-op', 'permit'),
LoopCase('for-unif-cond-break-op', 'sensitive'),
LoopCase('for-unif-always-return-op', 'permit'),
LoopCase('for-unif-cond-return-op', 'sensitive'),
LoopCase('for-unif-always-continue-op', 'permit'),
LoopCase('for-unif-cond-continue-op', 'sensitive'),
LoopCase('for-unif-op-always-break', 'permit'),
LoopCase('for-unif-op-cond-break', 'sensitive'),
LoopCase('for-unif-op-always-return', 'permit'),
LoopCase('for-unif-op-cond-return', 'sensitive'),
LoopCase('for-unif-op-always-continue', 'permit'),
LoopCase('for-unif-op-cond-continue', 'sensitive'),
LoopCase('for-unif-end-op', 'permit'),
LoopCase('for-unif-always-break-end-op', 'permit'),
LoopCase('for-unif-cond-break-end-op', 'permit'),
LoopCase('for-unif-always-return-end-op', 'permit'),
LoopCase('for-unif-cond-return-end-op', 'sensitive'),
LoopCase('for-unif-always-continue-end-op', 'permit'),
LoopCase('for-unif-cond-continue-end-op', 'permit'),
LoopCase('for-nonunif-op', 'forbid'),
LoopCase('for-nonunif-always-break-op', 'permit'),
LoopCase('for-nonunif-cond-break-op', 'forbid'),
LoopCase('for-nonunif-always-return-op', 'permit'),
LoopCase('for-nonunif-cond-return-op', 'forbid'),
LoopCase('for-nonunif-always-continue-op', 'permit'),
LoopCase('for-nonunif-cond-continue-op', 'forbid'),
LoopCase('for-nonunif-op-always-break', 'forbid'),
LoopCase('for-nonunif-op-cond-break', 'forbid'),
LoopCase('for-nonunif-op-always-return', 'forbid'),
LoopCase('for-nonunif-op-cond-return', 'forbid'),
LoopCase('for-nonunif-op-always-continue', 'forbid'),
LoopCase('for-nonunif-op-cond-continue', 'forbid'),
LoopCase('for-nonunif-end-op', 'permit'),
LoopCase('for-nonunif-always-break-end-op', 'permit'),
LoopCase('for-nonunif-cond-break-end-op', 'permit'),
LoopCase('for-nonunif-always-return-end-op', 'forbid'),
LoopCase('for-nonunif-cond-return-end-op', 'forbid'),
LoopCase('for-nonunif-always-continue-end-op', 'permit'),
LoopCase('for-nonunif-cond-continue-end-op', 'permit'),
LoopCase('while-unif-op', 'permit'),
LoopCase('while-unif-always-break-op', 'permit'),
LoopCase('while-unif-cond-break-op', 'sensitive'),
LoopCase('while-unif-always-return-op', 'permit'),
LoopCase('while-unif-cond-return-op', 'sensitive'),
LoopCase('while-unif-always-continue-op', 'permit'),
LoopCase('while-unif-cond-continue-op', 'sensitive'),
LoopCase('while-unif-op-always-break', 'permit'),
LoopCase('while-unif-op-cond-break', 'sensitive'),
LoopCase('while-unif-op-always-return', 'permit'),
LoopCase('while-unif-op-cond-return', 'sensitive'),
LoopCase('while-unif-op-always-continue', 'permit'),
LoopCase('while-unif-op-cond-continue', 'sensitive'),
LoopCase('while-unif-end-op', 'permit'),
LoopCase('while-unif-always-break-end-op', 'permit'),
LoopCase('while-unif-cond-break-end-op', 'permit'),
LoopCase('while-unif-always-return-end-op', 'permit'),
LoopCase('while-unif-cond-return-end-op', 'sensitive'),
LoopCase('while-unif-always-continue-end-op', 'permit'),
LoopCase('while-unif-cond-continue-end-op', 'permit'),
LoopCase('while-nonunif-op', 'forbid'),
LoopCase('while-nonunif-always-break-op', 'permit'),
LoopCase('while-nonunif-cond-break-op', 'forbid'),
LoopCase('while-nonunif-always-return-op', 'permit'),
LoopCase('while-nonunif-cond-return-op', 'forbid'),
LoopCase('while-nonunif-always-continue-op', 'permit'),
LoopCase('while-nonunif-cond-continue-op', 'forbid'),
LoopCase('while-nonunif-op-always-break', 'forbid'),
LoopCase('while-nonunif-op-cond-break', 'forbid'),
LoopCase('while-nonunif-op-always-return', 'forbid'),
LoopCase('while-nonunif-op-cond-return', 'forbid'),
LoopCase('while-nonunif-op-always-continue', 'forbid'),
LoopCase('while-nonunif-op-cond-continue', 'forbid'),
LoopCase('while-nonunif-end-op', 'permit'),
LoopCase('while-nonunif-always-break-end-op', 'permit'),
LoopCase('while-nonunif-cond-break-end-op', 'permit'),
LoopCase('while-nonunif-always-return-end-op', 'forbid'),
LoopCase('while-nonunif-cond-return-end-op', 'forbid'),
LoopCase('while-nonunif-always-continue-end-op', 'permit'),
LoopCase('while-nonunif-cond-continue-end-op', 'permit'),
];
const kStatementNames = kStatementCases.map(sc => sc.name);
type StatementName = (typeof kStatementNames)[number];
const kStatementDict = Object.fromEntries(kStatementCases.map(sc => [sc.name, sc])) as Record<
StatementName,
Snippet
>;
function generateConditionalStatement(
name: StatementName,
condition_name: string,
op_name: string
): Snippet {
const cond = generateCondition(condition_name);
const op = generateOp(op_name);
const snippet = kStatementDict[name];
let code = snippet.code;
code = code
.replace('<op>', op)
.replace('<cond>', cond)
.replaceAll('<uniform_cond>', generateCondition('uniform_storage_ro'))
.replaceAll('<nonuniform_cond>', generateCondition('nonuniform_storage_ro'));
return { name, code, verdict: snippet.verdict };
}
g.test('basics')
.desc(`Test collective operations in simple uniform or non-uniform control flow.`)
.params(u =>
u
.combine('statement', kStatementNames)
.beginSubcases()
.combineWithParams(kConditions)
.combineWithParams(kCollectiveOps)
)
.fn(t => {
if (t.params.op === 'textureBarrier' || t.params.cond.startsWith('storage_texture')) {
t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures');
}
let code = `
@group(0) @binding(0) var s : sampler;
@group(0) @binding(1) var s_comp : sampler_comparison;
@group(0) @binding(2) var tex : texture_2d<f32>;
@group(0) @binding(3) var tex_depth : texture_depth_2d;
@group(1) @binding(0) var<storage, read> ro_buffer : array<f32, 4>;
@group(1) @binding(1) var<storage, read_write> rw_buffer : array<f32, 4>;
@group(1) @binding(2) var<uniform> uniform_buffer : vec4<f32>;
@group(2) @binding(0) var ro_storage_texture : texture_storage_2d<rgba8unorm, read>;
@group(2) @binding(1) var rw_storage_texture : texture_storage_2d<rgba8unorm, read_write>;
var<private> priv_var : array<u32, 4> = array(0,0,0,0);
const c = false;
override o : f32;
`;
if (t.params.stage === 'compute') {
code += `var<workgroup> wg : f32;\n`;
code += ` @workgroup_size(16, 1, 1)`;
}
code += `@${t.params.stage}`;
code += `\nfn main(`;
if (t.params.stage === 'compute') {
code += `@builtin(global_invocation_id) p : vec3<u32>`;
} else {
code += `@builtin(position) p : vec4<f32>`;
}
code += `) {
let u_let = uniform_buffer.x;
let n_let = rw_buffer[0];
var u_f = uniform_buffer.z;
var n_f = rw_buffer[1];
`;
const snippet = generateConditionalStatement(t.params.statement, t.params.cond, t.params.op);
code += snippet.code;
code += `\n}\n`;
t.expectCompileResult(
compileShouldSucceed({
requires_uniformity: !t.params.op.startsWith('control_case'),
condition_is_uniform: t.params.expectation,
verdict: snippet.verdict,
}),
code
);
});
const kUniformSubgroupOps: readonly string[] = [
'subgroupAdd',
'subgroupMul',
'subgroupMax',
'subgroupMin',
'subgroupAll',
'subgroupAny',
'subgroupAnd',
'subgroupOr',
'subgroupXor',
'subgroupBallot',
'subgroupBroadcast',
'subgroupBroadcastFirst',
] as const;
const kSubgroupOps: readonly string[] = [
'control_case',
'subgroupInclusiveAdd',
'subgroupExclusiveAdd',
'subgroupInclusiveMul',
'subgroupExclusiveMul',
'subgroupElect',
'subgroupShuffle',
'subgroupShuffleUp',
'subgroupShuffleDown',
'subgroupShuffleXor',
'quadBroadcast',
'quadSwapX',
'quadSwapY',
'quadSwapDiagonal',
...kUniformSubgroupOps,
] as const;
g.test('basics,subgroups')
.desc(`Test subgroup operations in simple uniform or non-uniform control flow.`)
.params(u =>
u
.combine('statement', kStatementNames)
.beginSubcases()
.combineWithParams(kConditions)
.combine('op', kSubgroupOps)
.combine('stage', ['compute', 'fragment'] as const)
)
.fn(t => {
let code = `
enable subgroups;
@group(0) @binding(0) var s : sampler;
@group(0) @binding(1) var s_comp : sampler_comparison;
@group(0) @binding(2) var tex : texture_2d<f32>;
@group(0) @binding(3) var tex_depth : texture_depth_2d;
@group(1) @binding(0) var<storage, read> ro_buffer : array<f32, 4>;
@group(1) @binding(1) var<storage, read_write> rw_buffer : array<f32, 4>;
@group(1) @binding(2) var<uniform> uniform_buffer : vec4<f32>;
@group(2) @binding(0) var ro_storage_texture : texture_storage_2d<rgba8unorm, read>;
@group(2) @binding(1) var rw_storage_texture : texture_storage_2d<rgba8unorm, read_write>;
var<private> priv_var : array<u32, 4> = array(0,0,0,0);
const c = false;
override o : f32;
`;
if (t.params.stage === 'compute') {
code += `var<workgroup> wg : f32;\n`;
code += ` @workgroup_size(16, 1, 1)`;
}
code += `@${t.params.stage}`;
code += `\nfn main(`;
if (t.params.stage === 'compute') {
code += `@builtin(global_invocation_id) p : vec3<u32>`;
} else {
code += `@builtin(position) p : vec4<f32>`;
}
code += `) {
let u_let = uniform_buffer.x;
let n_let = rw_buffer[0];
var u_f = uniform_buffer.z;
var n_f = rw_buffer[1];
`;
const snippet = generateConditionalStatement(t.params.statement, t.params.cond, t.params.op);
code += snippet.code;
code += `\n}\n`;
t.expectCompileResult(
compileShouldSucceed({
requires_uniformity: !t.params.op.startsWith('control_case'),
condition_is_uniform: t.params.expectation,
verdict: snippet.verdict,
}),
code
);
});
g.test('uniform_subgroup_ops')
.desc(`Test subgroup operations that are uniform with subgroup uniformity.`)
.params(u => u.combine('op', kSubgroupOps).combine('scope', ['workgroup', 'subgroup'] as const))
.fn(t => {
const test_code =
t.params.scope === 'workgroup' ? 'workgroupBarrier();' : '_ = subgroupAny(true);';
const code = `
enable subgroups;
fn foo() {
if ${generateCondition(t.params.op)} {
${test_code}
}
}`;
const is_uniform =
kUniformSubgroupOps.includes(t.params.op) &&
t.hasLanguageFeature('subgroup_uniformity') &&
t.params.scope === 'subgroup';
t.expectCompileResult(
compileShouldSucceed({
requires_uniformity: !t.params.op.startsWith('control_case'),
condition_is_uniform: is_uniform,
verdict: 'sensitive',
}),
code
);
});
const kFragmentBuiltinValues = [
{
builtin: `position`,
type: `vec4<f32>`,
},
{
builtin: `front_facing`,
type: `bool`,
},
{
builtin: `sample_index`,
type: `u32`,
},
{
builtin: `sample_mask`,
type: `u32`,
},
{
builtin: `subgroup_invocation_id`,
type: `u32`,
},
{
builtin: `subgroup_size`,
type: `u32`,
},
{
builtin: `primitive_id`,
type: `u32`,
},
];
g.test('fragment_builtin_values')
.desc(`Test uniformity of fragment built-in values`)
.params(u => u.combineWithParams(kFragmentBuiltinValues).beginSubcases())
.beforeAllSubcases(t => {
t.skipIf(
t.isCompatibility && ['sample_index', 'sample_mask'].includes(t.params.builtin),
'compatibility mode does not support sample_index or sample_mask'
);
})
.fn(t => {
let cond = ``;
switch (t.params.type) {
case `u32`:
case `i32`:
case `f32`: {
cond = `p > 0`;
break;
}
case `vec4<u32>`:
case `vec4<i32>`:
case `vec4<f32>`: {
cond = `p.x > 0`;
break;
}
case `bool`: {
cond = `p`;
break;
}
default: {
unreachable(`Unhandled type`);
}
}
let enable = '';
if (t.params.builtin.includes('subgroup')) {
enable = 'enable subgroups;\n';
} else if (t.params.builtin === 'primitive_id') {
enable = 'enable chromium_experimental_primitive_id;\n';
}
const code = `
${enable}
@group(0) @binding(0) var s : sampler;
@group(0) @binding(1) var tex : texture_2d<f32>;
@fragment
fn main(@builtin(${t.params.builtin}) p : ${t.params.type}) {
if ${cond} {
let texel = textureSample(tex, s, vec2<f32>(0,0));
}
}
`;
t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
t.expectCompileResult(false, code);
});
const kComputeBuiltinValues = [
{
builtin: `local_invocation_id`,
type: `vec3<f32>`,
uniform: false,
},
{
builtin: `local_invocation_index`,
type: `u32`,
uniform: false,
},
{
builtin: `global_invocation_id`,
type: `vec3<u32>`,
uniform: false,
},
{
builtin: `workgroup_id`,
type: `vec3<u32>`,
uniform: true,
},
{
builtin: `num_workgroups`,
type: `vec3<u32>`,
uniform: true,
},
{
builtin: `subgroup_invocation_id`,
type: `u32`,
uniform: false,
},
{
builtin: `subgroup_size`,
type: `u32`,
uniform: true,
},
{
builtin: `subgroup_id`,
type: `u32`,
uniform: false,
},
{
builtin: `num_subgroups`,
type: `u32`,
uniform: true,
},
];
g.test('compute_builtin_values')
.desc(`Test uniformity of compute built-in values`)
.params(u =>
u
.combineWithParams(kComputeBuiltinValues)
.beginSubcases()
.combine('scope', ['workgroup', 'subgroup'] as const)
)
.beforeAllSubcases(t => {
if (t.params.builtin === `subgroup_id` || t.params.builtin === `num_subgroups`) {
t.skipIfLanguageFeatureNotSupported('subgroup_id');
}
if (t.params.scope === 'subgroup') {
t.skipIfLanguageFeatureNotSupported('subgroup_uniformity');
}
})
.fn(t => {
let cond = ``;
switch (t.params.type) {
case `u32`:
case `i32`:
case `f32`: {
cond = `p > 0`;
break;
}
case `vec3<u32>`:
case `vec3<i32>`:
case `vec3<f32>`: {
cond = `p.x > 0`;
break;
}
case `bool`: {
cond = `p`;
break;
}
default: {
unreachable(`Unhandled type`);
}
}
const enable =
t.params.builtin.includes('subgroup') || t.params.scope === 'subgroup'
? 'enable subgroups;'
: '';
const op = t.params.scope === 'workgroup' ? 'workgroupBarrier()' : '_ = subgroupAny(true)';
const code = `
${enable}
@compute @workgroup_size(16,1,1)
fn main(@builtin(${t.params.builtin}) p : ${t.params.type}) {
if ${cond} {
${op};
}
}
`;
const expect =
t.params.uniform || (t.params.builtin === 'subgroup_id' && t.params.scope === 'subgroup');
t.expectCompileResult(expect, code);
});
function generatePointerCheck(check: string): string {
if (check === `address`) {
return `let tmp = workgroupUniformLoad(ptr);`;
} else {
return `if test_val > 0 {
workgroupBarrier();
}`;
}
}
interface PointerCase {
code: string;
check: 'address' | 'contents';
uniform: boolean | 'never';
needs_deref_sugar?: boolean;
}
const kPointerCases: Record<string, PointerCase> = {
address_uniform_literal: {
code: `let ptr = &wg_array[0];`,
check: `address`,
uniform: true,
},
address_uniform_value: {
code: `let ptr = &wg_array[uniform_value];`,
check: `address`,
uniform: true,
},
address_nonuniform_value: {
code: `let ptr = &wg_array[nonuniform_value];`,
check: `address`,
uniform: false,
},
address_uniform_chain: {
code: `let p1 = &wg_struct.x;
let p2 = &(*p1)[uniform_value];
let p3 = &(*p2).x;
let ptr = &(*p3)[uniform_value];`,
check: `address`,
uniform: true,
},
address_nonuniform_chain1: {
code: `let p1 = &wg_struct.x;
let p2 = &(*p1)[nonuniform_value];
let p3 = &(*p2).x;
let ptr = &(*p3)[uniform_value];`,
check: `address`,
uniform: false,
},
address_nonuniform_chain2: {
code: `let p1 = &wg_struct.x;
let p2 = &(*p1)[uniform_value];
let p3 = &(*p2).x;
let ptr = &(*p3)[nonuniform_value];`,
check: `address`,
uniform: false,
},
wg_uniform_load_is_uniform: {
code: `let test_val = workgroupUniformLoad(&wg_scalar);`,
check: `contents`,
uniform: true,
},
wg_uniform_load_atomic_is_uniform: {
code: `let ptr = &wg_atomic;`,
check: `address`,
uniform: true,
},
contents_scalar_uniform1: {
code: `let ptr = &func_scalar;
let test_val = *ptr;`,
check: `contents`,
uniform: true,
},
contents_scalar_uniform2: {
code: `func_scalar = nonuniform_value;
let ptr = &func_scalar;
func_scalar = 0;
let test_val = *ptr;`,
check: `contents`,
uniform: true,
},
contents_scalar_uniform3: {
code: `let ptr = &func_scalar;
func_scalar = nonuniform_value;
func_scalar = uniform_value;
let test_val = *ptr;`,
check: `contents`,
uniform: true,
},
contents_scalar_nonuniform1: {
code: `func_scalar = nonuniform_value;
let ptr = &func_scalar;
let test_val = *ptr;`,
check: `contents`,
uniform: false,
},
contents_scalar_nonuniform2: {
code: `let ptr = &func_scalar;
*ptr = nonuniform_value;
let test_val = *ptr;`,
check: `contents`,
uniform: false,
},
contents_scalar_alias_uniform: {
code: `let p = &func_scalar;
let ptr = p;
let test_val = *ptr;`,
check: `contents`,
uniform: true,
},
contents_scalar_alias_nonuniform1: {
code: `func_scalar = nonuniform_value;
let p = &func_scalar;
let ptr = p;
let test_val = *ptr;`,
check: `contents`,
uniform: false,
},
contents_scalar_alias_nonuniform2: {
code: `let p = &func_scalar;
*p = nonuniform_value;
let ptr = p;
let test_val = *ptr;`,
check: `contents`,
uniform: false,
},
contents_scalar_alias_nonuniform3: {
code: `let p = &func_scalar;
let ptr = p;
*p = nonuniform_value;
let test_val = *ptr;`,
check: `contents`,
uniform: false,
},
contents_scalar_alias_nonuniform4: {
code: `let p = &func_scalar;
func_scalar = nonuniform_value;
let test_val = *p;`,
check: `contents`,
uniform: false,
},
contents_scalar_alias_nonuniform5: {
code: `let p = &func_scalar;
*p = nonuniform_value;
let test_val = func_scalar;`,
check: `contents`,
uniform: false,
},
contents_array_uniform_index: {
code: `let ptr = &func_array[uniform_value];
let test_val = *ptr;`,
check: `contents`,
uniform: true,
},
contents_array_nonuniform_index1: {
code: `let ptr = &func_array[nonuniform_value];
let test_val = *ptr;`,
check: `contents`,
uniform: false,
},
contents_array_nonuniform_index2: {
code: `let ptr = &func_array[lid.x];
let test_val = *ptr;`,
check: `contents`,
uniform: false,
},
contents_array_nonuniform_index3: {
code: `let ptr = &func_array[gid.x];
let test_val = *ptr;`,
check: `contents`,
uniform: false,
},
contents_struct_uniform: {
code: `let p1 = &func_struct.x[uniform_value].x[uniform_value].x[uniform_value];
let test_val = *p1;`,
check: `contents`,
uniform: true,
},
contents_struct_nonuniform1: {
code: `let p1 = &func_struct.x[nonuniform_value].x[uniform_value].x[uniform_value];
let test_val = *p1;`,
check: `contents`,
uniform: false,
},
contents_struct_nonuniform2: {
code: `let p1 = &func_struct.x[uniform_value].x[gid.x].x[uniform_value];
let test_val = *p1;`,
check: `contents`,
uniform: false,
},
contents_struct_nonuniform3: {
code: `let p1 = &func_struct.x[uniform_value].x[uniform_value].x[lid.y];
let test_val = *p1;`,
check: `contents`,
uniform: false,
},
contents_struct_chain_uniform: {
code: `let p1 = &func_struct.x;
let p2 = &(*p1)[uniform_value];
let p3 = &(*p2).x;
let p4 = &(*p3)[uniform_value];
let p5 = &(*p4).x;
let p6 = &(*p5)[uniform_value];
let test_val = *p6;`,
check: `contents`,
uniform: true,
},
contents_struct_chain_nonuniform1: {
code: `let p1 = &func_struct.x;
let p2 = &(*p1)[nonuniform_value];
let p3 = &(*p2).x;
let p4 = &(*p3)[uniform_value];
let p5 = &(*p4).x;
let p6 = &(*p5)[uniform_value];
let test_val = *p6;`,
check: `contents`,
uniform: false,
},
contents_struct_chain_nonuniform2: {
code: `let p1 = &func_struct.x;
let p2 = &(*p1)[uniform_value];
let p3 = &(*p2).x;
let p4 = &(*p3)[gid.x];
let p5 = &(*p4).x;
let p6 = &(*p5)[uniform_value];
let test_val = *p6;`,
check: `contents`,
uniform: false,
},
contents_struct_chain_nonuniform3: {
code: `let p1 = &func_struct.x;
let p2 = &(*p1)[uniform_value];
let p3 = &(*p2).x;
let p4 = &(*p3)[uniform_value];
let p5 = &(*p4).x;
let p6 = &(*p5)[lid.y];
let test_val = *p6;`,
check: `contents`,
uniform: false,
},
contents_lhs_ref_pointer_deref1: {
code: `*&func_scalar = uniform_value;
let test_val = func_scalar;`,
check: `contents`,
uniform: true,
},
contents_lhs_ref_pointer_deref1a: {
code: `*&func_scalar = nonuniform_value;
let test_val = func_scalar;`,
check: `contents`,
uniform: false,
},
contents_lhs_ref_pointer_deref2: {
code: `*&(func_array[nonuniform_value]) = uniform_value;
let test_val = func_array[0];`,
check: `contents`,
uniform: false,
},
contents_lhs_ref_pointer_deref2a: {
code: `(func_array[nonuniform_value]) = uniform_value;
let test_val = func_array[0];`,
check: `contents`,
uniform: false,
},
contents_lhs_ref_pointer_deref3: {
code: `*&(func_array[needs_uniform(uniform_value)]) = uniform_value;
let test_val = func_array[0];`,
check: `contents`,
uniform: true,
},
contents_lhs_ref_pointer_deref3a: {
code: `*&(func_array[needs_uniform(nonuniform_value)]) = uniform_value;
let test_val = func_array[0];`,
check: `contents`,
uniform: 'never',
},
contents_lhs_ref_pointer_deref4: {
code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[uniform_value]) = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: true,
},
contents_lhs_ref_pointer_deref4a: {
code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[uniform_value]) = nonuniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: false,
},
contents_lhs_ref_pointer_deref4b: {
code: `*&((*&(func_struct.x[uniform_value])).x[uniform_value].x[nonuniform_value]) = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: false,
},
contents_lhs_ref_pointer_deref4c: {
code: `*&((*&(func_struct.x[uniform_value])).x[nonuniform_value]).x[uniform_value] = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: false,
},
contents_lhs_ref_pointer_deref4d: {
code: `*&((*&(func_struct.x[nonuniform_value])).x[uniform_value].x)[uniform_value] = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: false,
},
contents_lhs_ref_pointer_deref4e: {
code: `*&((*&(func_struct.x[uniform_value])).x[needs_uniform(nonuniform_value)].x[uniform_value]) = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: 'never',
},
contents_lhs_pointer_deref2: {
code: `(&func_array)[uniform_value] = uniform_value;
let test_val = func_array[0];`,
check: `contents`,
uniform: true,
needs_deref_sugar: true,
},
contents_lhs_pointer_deref2a: {
code: `(&func_array)[nonuniform_value] = uniform_value;
let test_val = func_array[0];`,
check: `contents`,
uniform: false,
needs_deref_sugar: true,
},
contents_lhs_pointer_deref3: {
code: `(&func_array)[needs_uniform(uniform_value)] = uniform_value;
let test_val = func_array[0];`,
check: `contents`,
uniform: true,
needs_deref_sugar: true,
},
contents_lhs_pointer_deref3a: {
code: `(&func_array)[needs_uniform(nonuniform_value)] = uniform_value;
let test_val = func_array[0];`,
check: `contents`,
uniform: 'never',
needs_deref_sugar: true,
},
contents_lhs_pointer_deref4: {
code: `(&((&(func_struct.x[uniform_value])).x[uniform_value]).x)[uniform_value] = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: true,
needs_deref_sugar: true,
},
contents_lhs_pointer_deref4a: {
code: `(&((&(func_struct.x[uniform_value])).x[uniform_value]).x)[uniform_value] = nonuniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: false,
needs_deref_sugar: true,
},
contents_lhs_pointer_deref4b: {
code: `(&((&(func_struct.x[uniform_value])).x)[uniform_value]).x[nonuniform_value] = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: false,
needs_deref_sugar: true,
},
contents_lhs_pointer_deref4c: {
code: `(&((&(func_struct.x[uniform_value])).x[nonuniform_value]).x)[uniform_value] = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: false,
needs_deref_sugar: true,
},
contents_lhs_pointer_deref4d: {
code: `(&((&(func_struct.x[nonuniform_value])).x[uniform_value]).x)[uniform_value] = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: false,
needs_deref_sugar: true,
},
contents_lhs_pointer_deref4e: {
code: `(&((&(func_struct.x[uniform_value])).x)[needs_uniform(nonuniform_value)].x[uniform_value]) = uniform_value;
let test_val = func_struct.x[0].x[0].x[0];`,
check: `contents`,
uniform: 'never',
needs_deref_sugar: true,
},
contents_rhs_pointer_deref1: {
code: `let test_val = (&func_array)[uniform_value];`,
check: `contents`,
uniform: true,
needs_deref_sugar: true,
},
contents_rhs_pointer_deref1a: {
code: `let test_val = (&func_array)[nonuniform_value];`,
check: `contents`,
uniform: false,
needs_deref_sugar: true,
},
contents_rhs_pointer_deref2: {
code: `let test_val = (&func_array)[needs_uniform(nonuniform_value)];`,
check: `contents`,
uniform: `never`,
needs_deref_sugar: true,
},
contents_rhs_pointer_swizzle_uniform: {
code: `func_vector = vec4(uniform_value);
let test_val = dot((&func_vector).yw, vec2());`,
check: `contents`,
uniform: true,
needs_deref_sugar: true,
},
contents_rhs_pointer_swizzle_non_uniform: {
code: `func_vector = vec4(nonuniform_value);
let test_val = dot((&func_vector).yw, vec2());`,
check: `contents`,
uniform: false,
needs_deref_sugar: true,
},
};
g.test('pointers')
.desc(`Test pointer uniformity (contents and addresses)`)
.params(u => u.combine('case', keysOf(kPointerCases)).beginSubcases())
.fn(t => {
const testcase = kPointerCases[t.params.case];
const code = `
var<workgroup> wg_scalar : u32;
var<workgroup> wg_array : array<u32, 16>;
var<workgroup> wg_atomic : atomic<u32>;
struct Inner {
x : array<u32, 4>
}
struct Middle {
x : array<Inner, 4>
}
struct Outer {
x : array<Middle, 4>
}
var<workgroup> wg_struct : Outer;
@group(0) @binding(0)
var<storage> uniform_value : u32;
@group(0) @binding(1)
var<storage, read_write> nonuniform_value : u32;
fn needs_uniform(val : u32) -> u32{
if val == 0 {
workgroupBarrier();
}
return val;
}
@compute @workgroup_size(16, 1, 1)
fn main(@builtin(local_invocation_id) lid : vec3<u32>,
@builtin(global_invocation_id) gid : vec3<u32>) {
var func_scalar : u32;
var func_vector : vec4u;
var func_array : array<u32, 16>;
var func_struct : Outer;
${testcase.code}
`;
const with_check =
code +
`
${generatePointerCheck(testcase.check)}
}`;
if (testcase.needs_deref_sugar === true) {
t.skipIfLanguageFeatureNotSupported('pointer_composite_access');
}
if (testcase.uniform === false) {
const without_check = code + `}\n`;
t.expectCompileResult(true, without_check);
}
t.expectCompileResult(testcase.uniform === true, with_check);
});
function expectedUniformity(uniform: string, init: string): boolean {
if (uniform === `always`) {
return true;
} else if (uniform === `init`) {
return init === `no_init` || init === `uniform`;
}
return false;
}
interface FuncVarCase {
typename: string;
typedecl: string;
assignment: string;
cond: string;
uniform: string;
requires?: WGSLLanguageFeature;
}
const kFuncVarCases: Record<string, FuncVarCase> = {
no_assign: {
typename: `u32`,
typedecl: ``,
assignment: ``,
cond: `x > 0`,
uniform: `init`,
},
simple_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `x = uniform_value[0];`,
cond: `x > 0`,
uniform: `always`,
},
simple_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `x = nonuniform_value[0];`,
cond: `x > 0`,
uniform: `never`,
},
compound_assign_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `x += uniform_value[0];`,
cond: `x > 0`,
uniform: `init`,
},
compound_assign_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `x -= nonuniform_value[0];`,
cond: `x > 0`,
uniform: `never`,
},
unreachable_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
break;
x = uniform_value[0];
}`,
cond: `x > 0`,
uniform: `init`,
},
unreachable_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
break;
x = nonuniform_value[0];
}`,
cond: `x > 0`,
uniform: `init`,
},
if_no_else_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
x = uniform_value[0];
}`,
cond: `x > 0`,
uniform: `init`,
},
if_no_else_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
x = nonuniform_value[0];
}`,
cond: `x > 0`,
uniform: `never`,
},
if_no_then_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
} else {
x = uniform_value[0];
}`,
cond: `x > 0`,
uniform: `init`,
},
if_no_then_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
} else {
x = nonuniform_value[0];
}`,
cond: `x > 0`,
uniform: `never`,
},
if_else_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
x = uniform_value[0];
} else {
x = uniform_value[1];
}`,
cond: `x > 0`,
uniform: `always`,
},
if_else_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
x = nonuniform_value[0];
} else {
x = nonuniform_value[1];
}`,
cond: `x > 0`,
uniform: `never`,
},
if_else_split: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
x = uniform_value[0];
} else {
x = nonuniform_value[0];
}`,
cond: `x > 0`,
uniform: `never`,
},
if_unreachable_else_none: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
} else {
return;
}`,
cond: `x > 0`,
uniform: `init`,
},
if_unreachable_else_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
x = uniform_value[0];
} else {
return;
}`,
cond: `x > 0`,
uniform: `always`,
},
if_unreachable_else_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
x = nonuniform_value[0];
} else {
return;
}`,
cond: `x > 0`,
uniform: `never`,
},
if_unreachable_then_none: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
return;
}`,
cond: `x > 0`,
uniform: `init`,
},
if_unreachable_then_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
return;
} else {
x = uniform_value[0];
}`,
cond: `x > 0`,
uniform: `always`,
},
if_unreachable_then_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
return;
} else {
x = nonuniform_value[0];
}`,
cond: `x > 0`,
uniform: `never`,
},
if_nonescaping_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `if uniform_cond {
x = nonuniform_value[0];
return;
}`,
cond: `x > 0`,
uniform: `init`,
},
loop_body_depends_on_continuing_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
continuing {
x = uniform_value[0];
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `init`,
},
loop_body_depends_on_continuing_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
continuing {
x = nonuniform_value[0];
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `never`,
},
loop_body_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
x = uniform_value[0];
continuing {
break if uniform_cond;
}
}`,
cond: `x > 0`,
uniform: `always`,
},
loop_body_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
x = nonuniform_value[0];
continuing {
break if uniform_cond;
}
}`,
cond: `x > 0`,
uniform: `never`,
},
loop_body_nonuniform_cond: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
// The analysis doesn't recognize the content of the value.
x = uniform_value[0];
continuing {
break if nonuniform_cond;
}
}`,
cond: `x > 0`,
uniform: `never`,
},
loop_unreachable_continuing: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
break;
continuing {
break if uniform_cond;
}
}`,
cond: `x > 0`,
uniform: `init`,
},
loop_continuing_from_body_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
x = uniform_value[0];
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `always`,
},
loop_continuing_from_body_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
x = nonuniform_value[0];
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `never`,
},
loop_continuing_from_body_split1: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if uniform_cond {
x = uniform_value[0];
}
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `init`,
},
loop_continuing_from_body_split2: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if uniform_cond {
x = nonuniform_value[0];
}
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `never`,
},
loop_continuing_from_body_split3: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if uniform_cond {
x = uniform_value[0];
} else {
x = uniform_value[1];
}
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `always`,
},
loop_continuing_from_body_split4: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if nonuniform_cond {
x = uniform_value[0];
} else {
x = uniform_value[1];
}
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `never`,
},
loop_continuing_from_body_split5: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if nonuniform_cond {
x = uniform_value[0];
} else {
x = uniform_value[0];
}
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `never`,
},
loop_in_loop_with_continue_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
loop {
x = nonuniform_value[0];
if nonuniform_cond {
break;
}
continue;
}
x = uniform_value[0];
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `always`,
},
loop_in_loop_with_continue_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
loop {
x = uniform_value[0];
if uniform_cond {
break;
}
continue;
}
x = nonuniform_value[0];
continuing {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
break if uniform_cond;
}
}`,
cond: `true`,
uniform: `never`,
},
after_loop_with_uniform_break_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if uniform_cond {
x = uniform_value[0];
break;
}
}`,
cond: `x > 0`,
uniform: `always`,
},
after_loop_with_uniform_break_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if uniform_cond {
x = nonuniform_value[0];
break;
}
}`,
cond: `x > 0`,
uniform: `never`,
},
after_loop_with_nonuniform_break: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if nonuniform_cond {
x = uniform_value[0];
break;
}
}`,
cond: `x > 0`,
uniform: `never`,
},
after_loop_with_uniform_breaks: {
typename: `u32`,
typedecl: ``,
assignment: `loop {
if uniform_cond {
x = uniform_value[0];
break;
} else {
break;
}
}`,
cond: `x > 0`,
uniform: `init`,
},
switch_uniform_case: {
typename: `u32`,
typedecl: ``,
assignment: `switch uniform_val {
case 0 {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
}
default {
}
}`,
cond: `true`,
uniform: `init`,
},
switch_nonuniform_case: {
typename: `u32`,
typedecl: ``,
assignment: `switch nonuniform_val {
case 0 {
if x > 0 {
let tmp = textureSample(t, s, vec2f(0,0));
}
}
default {
}
}`,
cond: `true`,
uniform: `never`,
},
after_switch_all_uniform: {
typename: `u32`,
typedecl: ``,
assignment: `switch uniform_val {
case 0 {
x = uniform_value[0];
}
case 1,2 {
x = uniform_value[1];
}
default {
x = uniform_value[2];
}
}`,
cond: `x > 0`,
uniform: `always`,
},
after_switch_some_assign: {
typename: `u32`,
typedecl: ``,
assignment: `switch uniform_val {
case 0 {
x = uniform_value[0];
}
case 1,2 {
x = uniform_value[1];
}
default {
}
}`,
cond: `x > 0`,
uniform: `init`,
},
after_switch_nonuniform: {
typename: `u32`,
typedecl: ``,
assignment: `switch uniform_val {
case 0 {
x = uniform_value[0];
}
case 1,2 {
x = uniform_value[1];
}
default {
x = nonuniform_value[0];
}
}`,
cond: `x > 0`,
uniform: `never`,
},
after_switch_with_break_nonuniform1: {
typename: `u32`,
typedecl: ``,
assignment: `switch uniform_val {
default {
if uniform_cond {
x = uniform_value[0];
break;
}
x = nonuniform_value[0];
}
}`,
cond: `x > 0`,
uniform: `never`,
},
after_switch_with_break_nonuniform2: {
typename: `u32`,
typedecl: ``,
assignment: `switch uniform_val {
default {
x = uniform_value[0];
if uniform_cond {
x = nonuniform_value[0];
break;
}
}
}`,
cond: `x > 0`,
uniform: `never`,
},
for_loop_uniform_body: {
typename: `u32`,
typedecl: ``,
assignment: `for (var i = 0; i < 10; i += 1) {
x = uniform_value[0];
}`,
cond: `x > 0`,
uniform: `init`,
},
for_loop_nonuniform_body: {
typename: `u32`,
typedecl: ``,
assignment: `for (var i = 0; i < 10; i += 1) {
x = nonuniform_value[0];
}`,
cond: `x > 0`,
uniform: `never`,
},
for_loop_uniform_body_no_condition: {
typename: `u32`,
typedecl: ``,
assignment: `for (var i = 0; ; i += 1) {
x = uniform_value[0];
if uniform_cond {
break;
}
}`,
cond: `x > 0`,
uniform: `always`,
},
for_loop_nonuniform_body_no_condition: {
typename: `u32`,
typedecl: ``,
assignment: `for (var i = 0; ; i += 1) {
x = nonuniform_value[0];
if uniform_cond {
break;
}
}`,
cond: `x > 0`,
uniform: `never`,
},
for_loop_uniform_increment: {
typename: `u32`,
typedecl: ``,
assignment: `for (; uniform_cond; x += uniform_value[0]) {
}`,
cond: `x > 0`,
uniform: `init`,
},
for_loop_nonuniform_increment: {
typename: `u32`,
typedecl: ``,
assignment: `for (; uniform_cond; x += nonuniform_value[0]) {
}`,
cond: `x > 0`,
uniform: `never`,
},
for_loop_uniform_init: {
typename: `u32`,
typedecl: ``,
assignment: `for (x = uniform_value[0]; uniform_cond; ) {
}`,
cond: `x > 0`,
uniform: `always`,
},
for_loop_nonuniform_init: {
typename: `u32`,
typedecl: ``,
assignment: `for (x = nonuniform_value[0]; uniform_cond;) {
}`,
cond: `x > 0`,
uniform: `never`,
},
while_loop_uniform_body: {
typename: `u32`,
typedecl: ``,
assignment: `while uniform_cond {
x = uniform_value[0];
}`,
cond: `x > 0`,
uniform: `init`,
},
while_loop_nonuniform_body: {
typename: `u32`,
typedecl: ``,
assignment: `while uniform_cond {
x = nonuniform_value[0];
}`,
cond: `x > 0`,
uniform: `never`,
},
partial_assignment_uniform: {
typename: `block`,
typedecl: `struct block {
x : u32,
y : u32
}`,
assignment: `x.x = uniform_value[0].x;`,
cond: `x.x > 0`,
uniform: `init`,
},
partial_assignment_nonuniform: {
typename: `block`,
typedecl: `struct block {
x : u32,
y : u32
}`,
assignment: `x.x = nonuniform_value[0].x;`,
cond: `x.x > 0`,
uniform: `never`,
},
partial_assignment_all_members_uniform: {
typename: `block`,
typedecl: `struct block {
x : u32,
y : u32
}`,
assignment: `x.x = uniform_value[0].x;
x.y = uniform_value[1].y;`,
cond: `x.x > 0`,
uniform: `init`,
},
partial_assignment_all_members_nonuniform: {
typename: `block`,
typedecl: `struct block {
x : u32,
y : u32
}`,
assignment: `x.x = nonuniform_value[0].x;
x.y = uniform_value[0].x;`,
cond: `x.x > 0`,
uniform: `never`,
},
partial_assignment_single_element_struct_uniform: {
typename: `block`,
typedecl: `struct block {
x : u32
}`,
assignment: `x.x = uniform_value[0].x;`,
cond: `x.x > 0`,
uniform: `init`,
},
partial_assignment_single_element_struct_nonuniform: {
typename: `block`,
typedecl: `struct block {
x : u32
}`,
assignment: `x.x = nonuniform_value[0].x;`,
cond: `x.x > 0`,
uniform: `never`,
},
partial_assignment_single_element_array_uniform: {
typename: `array<u32, 1>`,
typedecl: ``,
assignment: `x[0] = uniform_value[0][0];`,
cond: `x[0] > 0`,
uniform: `init`,
},
partial_assignment_single_element_array_nonuniform: {
typename: `array<u32, 1>`,
typedecl: ``,
assignment: `x[0] = nonuniform_value[0][0];`,
cond: `x[0] > 0`,
uniform: `never`,
},
nested1: {
typename: `block`,
typedecl: `struct block {
x : u32,
y : u32
}`,
assignment: `for (; uniform_cond; ) {
if uniform_cond {
x = uniform_value[0];
break;
x.y = nonuniform_value[0].y;
} else {
if uniform_cond {
continue;
}
x = uniform_value[1];
}
}`,
cond: `x.x > 0`,
uniform: `init`,
},
nested2: {
typename: `block`,
typedecl: `struct block {
x : u32,
y : u32
}`,
assignment: `for (; uniform_cond; ) {
if uniform_cond {
x = uniform_value[0];
break;
x.y = nonuniform_value[0].y;
} else {
if nonuniform_cond {
continue;
}
x = uniform_value[1];
}
}`,
cond: `x.x > 0`,
uniform: `never`,
},
full_assignment_vec_uniform: {
typename: `vec3u`,
typedecl: ``,
assignment: `x = uniform_value[0];`,
cond: `x.x > 0`,
uniform: `always`,
},
full_assignment_vec_nonuniform: {
typename: `vec3u`,
typedecl: ``,
assignment: `x = nonuniform_value[0];`,
cond: `x.x > 0`,
uniform: `never`,
},
partial_assignment_vec_uniform: {
typename: `vec3u`,
typedecl: ``,
assignment: `x.x = uniform_value[0].x;`,
cond: `x.x > 0`,
uniform: `init`,
},
partial_assignment_vec_nonuniform: {
typename: `vec3u`,
typedecl: ``,
assignment: `x.x = nonuniform_value[0].x;`,
cond: `x.x > 0`,
uniform: `never`,
},
partial_assignment_vec_all_components_uniform: {
typename: `vec3u`,
typedecl: ``,
assignment: `x.x = uniform_value[0].x;
x.y = uniform_value[0].y;
x.z = uniform_value[0].z;`,
cond: `x.x > 0`,
uniform: `init`,
},
partial_assignment_vec_all_components_nonuniform: {
typename: `vec3u`,
typedecl: ``,
assignment: `x.x = nonuniform_value[0].x;
x.y = uniform_value[0].y;
x.z = uniform_value[0].z;`,
cond: `x.x > 0`,
uniform: `never`,
},
full_swizzle_assignment_uniform: {
requires: 'swizzle_assignment',
typename: `vec3u`,
typedecl: ``,
assignment: `x.xyz = uniform_value[0].xyz;`,
cond: `x.x > 0`,
uniform: `always`,
},
full_swizzle_assignment_nonuniform: {
typename: `vec3u`,
typedecl: ``,
assignment: `x.xyz = nonuniform_value[0].xyz;`,
cond: `x.x > 0`,
uniform: `never`,
requires: 'swizzle_assignment',
},
full_swizzle_assignment_permutation_uniform: {
requires: 'swizzle_assignment',
typename: `vec3u`,
typedecl: ``,
assignment: `x.zyx = uniform_value[0].xyz;`,
cond: `x.x > 0`,
uniform: `always`,
},
full_swizzle_assignment_permutation_nonuniform: {
requires: 'swizzle_assignment',
typename: `vec3u`,
typedecl: ``,
assignment: `x.zyx = nonuniform_value[0].xyz;`,
cond: `x.x > 0`,
uniform: `never`,
},
partial_swizzle_assignment_uniform: {
requires: 'swizzle_assignment',
typename: `vec3u`,
typedecl: ``,
assignment: `x.xy = uniform_value[0].xy;`,
cond: `x.x > 0`,
uniform: `init`,
},
partial_swizzle_assignment_nonuniform: {
requires: 'swizzle_assignment',
typename: `vec3u`,
typedecl: ``,
assignment: `x.xy = nonuniform_value[0].xy;`,
cond: `x.x > 0`,
uniform: `never`,
},
chained_full_swizzle_assignment_uniform: {
requires: 'swizzle_assignment',
typename: `vec3u`,
typedecl: ``,
assignment: `x.xyz.zyx = uniform_value[0].xyz;`,
cond: `x.x > 0`,
uniform: `always`,
},
chained_partial_swizzle_assignment_uniform: {
requires: 'swizzle_assignment',
typename: `vec3u`,
typedecl: ``,
assignment: `x.xyz.xy = uniform_value[0].xy;`,
cond: `x.x > 0`,
uniform: `init`,
},
chained_full_swizzle_assignment_nonuniform: {
requires: 'swizzle_assignment',
typename: `vec3u`,
typedecl: ``,
assignment: `x.xyz.zyx = nonuniform_value[0].xyz;`,
cond: `x.x > 0`,
uniform: `never`,
},
};
const kVarInit = {
no_init: ``,
uniform: `= uniform_value[3];`,
nonuniform: `= nonuniform_value[3];`,
};
g.test('function_variables')
.desc(`Test uniformity of function variables`)
.params(u => u.combine('case', keysOf(kFuncVarCases)).combine('init', keysOf(kVarInit)))
.fn(t => {
const func_case = kFuncVarCases[t.params.case];
if (func_case.requires) {
t.skipIfLanguageFeatureNotSupported(func_case.requires);
}
const code = `
${func_case.typedecl}
@group(0) @binding(0)
var<storage> uniform_value : array<${func_case.typename}, 4>;
@group(0) @binding(1)
var<storage, read_write> nonuniform_value : array<${func_case.typename}, 4>;
@group(1) @binding(0)
var t : texture_2d<f32>;
@group(1) @binding(1)
var s : sampler;
var<private> nonuniform_cond : bool = true;
const uniform_cond : bool = true;
var<private> nonuniform_val : u32 = 0;
const uniform_val : u32 = 0;
@fragment
fn main() {
var x : ${func_case.typename} ${kVarInit[t.params.init]};
${func_case.assignment}
if ${func_case.cond} {
let tmp = textureSample(t, s, vec2f(0,0));
}
}
`;
const result = expectedUniformity(func_case.uniform, t.params.init);
if (!result) {
t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
}
t.expectCompileResult(result, code);
});
const kShortCircuitExpressionCases = {
or_uniform_uniform: {
code: `
let x = uniform_cond || uniform_cond;
if x {
let tmp = textureSample(t, s, vec2f(0,0));
}
`,
uniform: true,
},
or_uniform_nonuniform: {
code: `
let x = uniform_cond || nonuniform_cond;
if x {
let tmp = textureSample(t, s, vec2f(0,0));
}
`,
uniform: false,
},
or_nonuniform_uniform: {
code: `
let x = nonuniform_cond || uniform_cond;
if x {
let tmp = textureSample(t, s, vec2f(0,0));
}
`,
uniform: false,
},
or_nonuniform_nonuniform: {
code: `
let x = nonuniform_cond || nonuniform_cond;
if x {
let tmp = textureSample(t, s, vec2f(0,0));
}
`,
uniform: false,
},
or_uniform_first_nonuniform: {
code: `
let x = textureSample(t, s, vec2f(0,0)).x == 0 || nonuniform_cond;
`,
uniform: true,
},
or_uniform_second_nonuniform: {
code: `
let x = nonuniform_cond || textureSample(t, s, vec2f(0,0)).x == 0;
`,
uniform: false,
},
and_uniform_uniform: {
code: `
let x = uniform_cond && uniform_cond;
if x {
let tmp = textureSample(t, s, vec2f(0,0));
}
`,
uniform: true,
},
and_uniform_nonuniform: {
code: `
let x = uniform_cond && nonuniform_cond;
if x {
let tmp = textureSample(t, s, vec2f(0,0));
}
`,
uniform: false,
},
and_nonuniform_uniform: {
code: `
let x = nonuniform_cond && uniform_cond;
if x {
let tmp = textureSample(t, s, vec2f(0,0));
}
`,
uniform: false,
},
and_nonuniform_nonuniform: {
code: `
let x = nonuniform_cond && nonuniform_cond;
if x {
let tmp = textureSample(t, s, vec2f(0,0));
}
`,
uniform: false,
},
and_uniform_first_nonuniform: {
code: `
let x = textureSample(t, s, vec2f(0,0)).x == 0 && nonuniform_cond;
`,
uniform: true,
},
and_uniform_second_nonuniform: {
code: `
let x = nonuniform_cond && textureSample(t, s, vec2f(0,0)).x == 0;
`,
uniform: false,
},
};
const kPointerParamCases = {
pointer_uniform_passthrough_value: {
function: `fn foo(p : ptr<function, u32>) -> u32 {
return *p;
}`,
call: `var x = uniform_values[0];
let call = foo(&x);`,
cond: `x > 0`,
uniform: true,
},
pointer_nonuniform_passthrough_value: {
function: `fn foo(p : ptr<function, u32>) -> u32 {
return *p;
}`,
call: `var x = uniform_values[0];
let call = foo(&x);`,
cond: `x > 0`,
uniform: true,
},
pointer_store_uniform_value: {
function: `fn foo(p : ptr<function, u32>) {
*p = uniform_values[0];
}`,
call: `var x = nonuniform_values[0];
foo(&x);`,
cond: `x > 0`,
uniform: true,
},
pointer_store_nonuniform_value: {
function: `fn foo(p : ptr<function, u32>) {
*p = nonuniform_values[0];
}`,
call: `var x = uniform_values[0];
foo(&x);`,
cond: `x > 0`,
uniform: false,
},
pointer_depends_on_nonpointer_param_uniform: {
function: `fn foo(p : ptr<function, u32>, x : u32) {
*p = x;
}`,
call: `var x = nonuniform_values[0];
foo(&x, uniform_values[0]);`,
cond: `x > 0`,
uniform: true,
},
pointer_depends_on_nonpointer_param_nonuniform: {
function: `fn foo(p : ptr<function, u32>, x : u32) {
*p = x;
}`,
call: `var x = uniform_values[0];
foo(&x, nonuniform_values[0]);`,
cond: `x > 0`,
uniform: false,
},
pointer_depends_on_pointer_param_uniform: {
function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
*p = *q;
}`,
call: `var x = nonuniform_values[0];
var y = uniform_values[0];
foo(&x, &y);`,
cond: `x > 0`,
uniform: true,
},
pointer_depends_on_pointer_param_nonuniform: {
function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
*p = *q;
}`,
call: `var x = uniform_values[0];
var y = nonuniform_values[0];
foo(&x, &y);`,
cond: `x > 0`,
uniform: false,
},
pointer_codependent1: {
function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
if *p > 0 {
*p = *q;
} else {
*q++;
}
}`,
call: `var x = uniform_values[0];
var y = uniform_values[1];
foo(&x, &y);
let a = x + y;`,
cond: `a > 0`,
uniform: true,
},
pointer_codependent2: {
function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
if *p > 0 {
*p = *q;
} else {
*q++;
}
}`,
call: `var x = uniform_values[0];
var y = nonuniform_values[1];
foo(&x, &y);
let a = x + y;`,
cond: `a > 0`,
uniform: false,
},
pointer_codependent3: {
function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
if *p > 0 {
*p = *q;
} else {
*q++;
}
}`,
call: `var x = nonuniform_values[0];
var y = uniform_values[1];
foo(&x, &y);
let a = x + y;`,
cond: `a > 0`,
uniform: false,
},
pointer_codependent4: {
function: `fn foo(p : ptr<function, u32>, q : ptr<function, u32>) {
if *p > 0 {
*p = *q;
} else {
*q++;
}
}`,
call: `var x = nonuniform_values[0];
var y = nonuniform_values[1];
foo(&x, &y);
let a = x + y;`,
cond: `a > 0`,
uniform: false,
},
uniform_param_uniform_assignment: {
function: `fn foo(p : ptr<function, array<u32, 2>>, idx : u32) {
(*p)[idx] = uniform_values[0];
}`,
call: `var x = array(uniform_values[0], uniform_values[1]);
foo(&x, uniform_values[3]);`,
cond: `x[0] > 0`,
uniform: true,
},
uniform_param_nonuniform_assignment: {
function: `fn foo(p : ptr<function, array<u32, 2>>, idx : u32) {
(*p)[idx] = nonuniform_values[0];
}`,
call: `var x = array(uniform_values[0], uniform_values[1]);
foo(&x, uniform_values[3]);`,
cond: `x[0] > 0`,
uniform: false,
},
nonuniform_param_uniform_assignment: {
function: `fn foo(p : ptr<function, array<u32, 2>>, idx : u32) {
(*p)[idx] = uniform_values[0];
}`,
call: `var x = array(uniform_values[0], uniform_values[1]);
foo(&x, u32(clamp(pos.x, 0, 1)));`,
cond: `x[0] > 0`,
uniform: false,
},
nonuniform_param_nonuniform_assignment: {
function: `fn foo(p : ptr<function, array<u32, 2>>, idx : u32) {
(*p)[idx] = nonuniform_values[0];
}`,
call: `var x = array(uniform_values[0], uniform_values[1]);
foo(&x, u32(clamp(pos.x, 0, 1)));`,
cond: `x[0] > 0`,
uniform: false,
},
required_uniform_success: {
function: `fn foo(p : ptr<function, u32>) {
if *p > 0 {
let tmp = textureSample(t,s,vec2f(0,0));
}
}`,
call: `var x = uniform_values[0];
foo(&x);`,
cond: `uniform_cond`,
uniform: true,
},
required_uniform_failure: {
function: `fn foo(p : ptr<function, u32>) {
if *p > 0 {
let tmp = textureSample(t,s,vec2f(0,0));
}
}`,
call: `var x = nonuniform_values[0];
foo(&x);`,
cond: `uniform_cond`,
uniform: false,
},
uniform_conditional_call_assign_uniform: {
function: `fn foo(p : ptr<function, u32>) {
*p = uniform_values[0];
}`,
call: `var x = uniform_values[1];
if uniform_cond {
foo(&x);
}`,
cond: `x > 0`,
uniform: true,
},
uniform_conditional_call_assign_nonuniform1: {
function: `fn foo(p : ptr<function, u32>) {
*p = nonuniform_values[0];
}`,
call: `var x = uniform_values[1];
if uniform_cond {
foo(&x);
}`,
cond: `x > 0`,
uniform: false,
},
uniform_conditional_call_assign_nonuniform2: {
function: `fn foo(p : ptr<function, u32>) {
*p = uniform_values[0];
}`,
call: `var x = nonuniform_values[1];
if uniform_cond {
foo(&x);
}`,
cond: `x > 0`,
uniform: false,
},
nonuniform_conditional_call_assign_uniform: {
function: `fn foo(p : ptr<function, u32>) {
*p = uniform_values[0];
}`,
call: `var x = uniform_values[1];
if nonuniform_cond {
foo(&x);
}`,
cond: `x > 0`,
uniform: false,
},
};
g.test('function_pointer_parameters')
.desc(`Test functions and calls with pointer parameters`)
.params(u => u.combine('case', keysOf(kPointerParamCases)))
.fn(t => {
const pointer_case = kPointerParamCases[t.params.case];
const code = `
@group(0) @binding(0)
var t : texture_2d<f32>;
@group(0) @binding(1)
var s : sampler;
const uniform_cond = true;
var<private> nonuniform_cond = true;
@group(1) @binding(0)
var<storage> uniform_values : array<u32, 4>;
@group(1) @binding(1)
var<storage, read_write> nonuniform_values : array<u32, 4>;
${pointer_case.function}
@fragment
fn main(@builtin(position) pos : vec4f) {
${pointer_case.call}
if ${pointer_case.cond} {
let tmp = textureSample(t,s,vec2f(0,0));
}
}
`;
const res = pointer_case.uniform;
if (!res) {
t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
}
t.expectCompileResult(res, code);
});
g.test('short_circuit_expressions')
.desc(`Test uniformity of expressions`)
.params(u => u.combine('case', keysOf(kShortCircuitExpressionCases)))
.fn(t => {
const testcase = kShortCircuitExpressionCases[t.params.case];
const code = `
@group(1) @binding(0)
var t : texture_2d<f32>;
@group(1) @binding(1)
var s : sampler;
const uniform_cond = true;
var<private> nonuniform_cond = false;
@fragment
fn main() {
${testcase.code}
}
`;
const res = testcase.uniform;
if (!res) {
t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
}
t.expectCompileResult(res, code);
});
const kExpressionCases = {
literal: {
code: `1u`,
uniform: true,
},
uniform: {
code: `uniform_val`,
uniform: true,
},
nonuniform: {
code: `nonuniform_val`,
uniform: false,
},
uniform_index: {
code: `uniform_value[uniform_val]`,
uniform: true,
},
nonuniform_index1: {
code: `uniform_value[nonuniform_val]`,
uniform: false,
},
nonuniform_index2: {
code: `nonuniform_value[uniform_val]`,
uniform: false,
},
uniform_struct: {
code: `uniform_struct.x`,
uniform: true,
},
nonuniform_struct: {
code: `nonuniform_struct.x`,
uniform: false,
},
};
const kBinOps = {
plus: {
code: '+',
test: '> 0',
},
minus: {
code: '-',
test: '> 0',
},
times: {
code: '*',
test: '> 0',
},
div: {
code: '/',
test: '> 0',
},
rem: {
code: '%',
test: '> 0',
},
and: {
code: '&',
test: '> 0',
},
or: {
code: '|',
test: '> 0',
},
xor: {
code: '^',
test: '> 0',
},
shl: {
code: '<<',
test: '> 0',
},
shr: {
code: '>>',
test: '> 0',
},
less: {
code: '<',
test: '',
},
lessequal: {
code: '<=',
test: '',
},
greater: {
code: '>',
test: '',
},
greaterequal: {
code: '>=',
test: '',
},
equal: {
code: '==',
test: '',
},
notequal: {
code: '!=',
test: '',
},
};
g.test('binary_expressions')
.desc(`Test uniformity of binary expressions`)
.params(u =>
u
.combine('e1', keysOf(kExpressionCases))
.combine('e2', keysOf(kExpressionCases))
.beginSubcases()
.combine('op', keysOf(kBinOps))
)
.fn(t => {
const e1 = kExpressionCases[t.params.e1];
const e2 = kExpressionCases[t.params.e2];
const op = kBinOps[t.params.op];
const code = `
@group(0) @binding(0)
var t : texture_2d<f32>;
@group(0) @binding(1)
var s : sampler;
struct S {
x : u32
}
const uniform_struct = S(1);
var<private> nonuniform_struct = S(1);
const uniform_value : array<u32, 2> = array(1,1);
var<private> nonuniform_value : array<u32, 2> = array(1,1);
const uniform_val : u32 = 1;
var<private> nonuniform_val : u32 = 1;
@fragment
fn main() {
let tmp = ${e1.code} ${op.code} ${e2.code};
if tmp ${op.test} {
let res = textureSample(t, s, vec2f(0,0));
}
}
`;
const res = e1.uniform && e2.uniform;
if (!res) {
t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
}
t.expectCompileResult(res, code);
});
g.test('unary_expressions')
.desc(`Test uniformity of uniary expressions`)
.params(u =>
u
.combine('e', keysOf(kExpressionCases))
.combine('op', ['!b_tmp', '~i_tmp > 0', '-i32(i_tmp) > 0'] as const)
)
.fn(t => {
const e = kExpressionCases[t.params.e];
const code = `
@group(0) @binding(0)
var t : texture_2d<f32>;
@group(0) @binding(1)
var s : sampler;
struct S {
x : i32
}
const uniform_struct = S(1);
var<private> nonuniform_struct = S(1);
const uniform_value : array<i32, 2> = array(1,1);
var<private> nonuniform_value : array<i32, 2> = array(1,1);
const uniform_val : i32 = 1;
var<private> nonuniform_val : i32 = 1;
@fragment
fn main() {
let i_tmp = ${e.code};
let b_tmp = bool(i_tmp);
let tmp = ${t.params.op};
if tmp {
let res = textureSample(t, s, vec2f(0,0));
}
}
`;
const res = e.uniform;
if (!res) {
t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
}
t.expectCompileResult(res, code);
});
const kFunctionCases = {
uniform_result: {
function: `fn foo() -> u32 {
return uniform_values[0];
}`,
call: `let call = foo();`,
cond: `call > 0`,
uniform: true,
},
nonuniform_result: {
function: `fn foo() -> u32 {
return nonuniform_values[0];
}`,
call: `let call = foo();`,
cond: `call > 0`,
uniform: false,
},
nonuniform_return_is_uniform_after_call: {
function: `fn foo() {
if nonuniform_values[0] > 0 {
return;
} else {
return;
}
}`,
call: `foo();`,
cond: `uniform_cond`,
uniform: true,
},
uniform_passthrough_parameter: {
function: `fn foo(x : u32) -> u32 {
return x;
}`,
call: `let call = foo(uniform_values[0]);`,
cond: `call > 0`,
uniform: true,
},
nonuniform_passthrough_parameter: {
function: `fn foo(x : u32) -> u32 {
return x;
}`,
call: `let call = foo(nonuniform_values[0]);`,
cond: `call > 0`,
uniform: false,
},
combined_parameters1: {
function: `fn foo(x : u32, y : u32) -> u32 {
return x + y;
}`,
call: `let call = foo(uniform_values[0], uniform_values[1]);`,
cond: `call > 0`,
uniform: true,
},
combined_parameters2: {
function: `fn foo(x : u32, y : u32) -> u32 {
return x + y;
}`,
call: `let call = foo(nonuniform_values[0], uniform_values[1]);`,
cond: `call > 0`,
uniform: false,
},
combined_parameters3: {
function: `fn foo(x : u32, y : u32) -> u32 {
return x + y;
}`,
call: `let call = foo(uniform_values[0], nonuniform_values[1]);`,
cond: `call > 0`,
uniform: false,
},
combined_parameters4: {
function: `fn foo(x : u32, y : u32) -> u32 {
return x + y;
}`,
call: `let call = foo(nonuniform_values[0], nonuniform_values[1]);`,
cond: `call > 0`,
uniform: false,
},
uniform_parameter_cf_after_nonuniform_expr: {
function: `fn foo(x : bool, y : vec4f) -> f32 {
return select(0, y.x, x);
}`,
call: `let call = foo(nonuniform_cond || uniform_cond, textureSample(t,s,vec2f(0,0)));`,
cond: `uniform_cond`,
uniform: true,
},
required_uniform_function_call_in_uniform_cf: {
function: `fn foo() -> vec4f {
return textureSample(t,s,vec2f(0,0));
}`,
call: `if uniform_cond {
let call = foo();
}`,
cond: `uniform_cond`,
uniform: true,
},
required_uniform_function_call_in_nonuniform_cf: {
function: `fn foo() -> vec4f {
return textureSample(t,s,vec2f(0,0));
}`,
call: `if nonuniform_cond {
let call = foo();
}`,
cond: `uniform_cond`,
uniform: false,
},
required_uniform_function_call_in_nonuniform_cf2: {
function: `@diagnostic(warning, derivative_uniformity)
fn foo() -> vec4f {
return textureSample(t,s,vec2f(0,0));
}`,
call: `if nonuniform_cond {
let call = foo();
let sample = textureSample(t,s,vec2f(0,0));
}`,
cond: `uniform_cond`,
uniform: false,
},
required_uniform_function_call_depends_on_uniform_param: {
function: `fn foo(x : bool) -> vec4f {
if x {
return textureSample(t,s,vec2f(0,0));
}
return vec4f(0);
}`,
call: `let call = foo(uniform_cond);`,
cond: `uniform_cond`,
uniform: true,
},
required_uniform_function_call_depends_on_nonuniform_param: {
function: `fn foo(x : bool) -> vec4f {
if x {
return textureSample(t,s,vec2f(0,0));
}
return vec4f(0);
}`,
call: `let call = foo(nonuniform_cond);`,
cond: `uniform_cond`,
uniform: false,
},
dpdx_nonuniform_result: {
function: ``,
call: `let call = dpdx(1);`,
cond: `call > 0`,
uniform: false,
},
dpdy_nonuniform_result: {
function: ``,
call: `let call = dpdy(1);`,
cond: `call > 0`,
uniform: false,
},
dpdxCoarse_nonuniform_result: {
function: ``,
call: `let call = dpdxCoarse(1);`,
cond: `call > 0`,
uniform: false,
},
dpdyCoarse_nonuniform_result: {
function: ``,
call: `let call = dpdyCoarse(1);`,
cond: `call > 0`,
uniform: false,
},
dpdxFine_nonuniform_result: {
function: ``,
call: `let call = dpdxFine(1);`,
cond: `call > 0`,
uniform: false,
},
dpdyFine_nonuniform_result: {
function: ``,
call: `let call = dpdyFine(1);`,
cond: `call > 0`,
uniform: false,
},
fwidth_nonuniform_result: {
function: ``,
call: `let call = fwidth(1);`,
cond: `call > 0`,
uniform: false,
},
fwidthCoarse_nonuniform_result: {
function: ``,
call: `let call = fwidthCoarse(1);`,
cond: `call > 0`,
uniform: false,
},
fwidthFine_nonuniform_result: {
function: ``,
call: `let call = fwidthFine(1);`,
cond: `call > 0`,
uniform: false,
},
textureSample_nonuniform_result: {
function: ``,
call: `let call = textureSample(t,s,vec2f(0,0));`,
cond: `call.x > 0`,
uniform: false,
},
textureSampleBias_nonuniform_result: {
function: ``,
call: `let call = textureSampleBias(t,s,vec2f(0,0), 0);`,
cond: `call.x > 0`,
uniform: false,
},
textureSampleCompare_nonuniform_result: {
function: ``,
call: `let call = textureSampleCompare(td,sd,vec2f(0,0), 0);`,
cond: `call > 0`,
uniform: false,
},
textureDimensions_uniform_input_uniform_result: {
function: ``,
call: `let call = textureDimensions(t);`,
cond: `call.x > 0`,
uniform: true,
},
textureGather_uniform_input_uniform_result: {
function: ``,
call: `let call = textureGather(0,t,s,vec2f(0,0));`,
cond: `call.x > 0`,
uniform: true,
},
textureGatherCompare_uniform_input_uniform_result: {
function: ``,
call: `let call = textureGatherCompare(td,sd,vec2f(0,0), 0);`,
cond: `call.x > 0`,
uniform: true,
},
textureLoad_uniform_input_uniform_result: {
function: ``,
call: `let call = textureLoad(t,vec2u(0,0),0);`,
cond: `call.x > 0`,
uniform: true,
},
textureNumLayers_uniform_input_uniform_result: {
function: ``,
call: `let call = textureNumLayers(ta);`,
cond: `call > 0`,
uniform: true,
},
textureNumLevels_uniform_input_uniform_result: {
function: ``,
call: `let call = textureNumLevels(t);`,
cond: `call > 0`,
uniform: true,
},
textureNumSamples_uniform_input_uniform_result: {
function: ``,
call: `let call = textureNumSamples(ts);`,
cond: `call > 0`,
uniform: true,
},
textureSampleLevel_uniform_input_uniform_result: {
function: ``,
call: `let call = textureSampleLevel(t,s,vec2f(0,0),0);`,
cond: `call.x > 0`,
uniform: true,
},
textureSampleGrad_uniform_input_uniform_result: {
function: ``,
call: `let call = textureSampleGrad(t,s,vec2f(0,0),vec2f(0,0),vec2f(0,0));`,
cond: `call.x > 0`,
uniform: true,
},
textureSampleCompareLevel_uniform_input_uniform_result: {
function: ``,
call: `let call = textureSampleCompareLevel(td,sd,vec2f(0,0), 0);`,
cond: `call > 0`,
uniform: true,
},
textureSampleBaseClampToEdge_uniform_input_uniform_result: {
function: ``,
call: `let call = textureSampleBaseClampToEdge(t,s,vec2f(0,0));`,
cond: `call.x > 0`,
uniform: true,
},
min_uniform_input_uniform_result: {
function: ``,
call: `let call = min(0,0);`,
cond: `call > 0`,
uniform: true,
},
value_constructor_uniform_input_uniform_result: {
function: ``,
call: `let call = vec2u(0,0);`,
cond: `call.x > 0`,
uniform: true,
},
};
g.test('functions')
.desc(`Test uniformity of function calls (non-pointer parameters)`)
.params(u => u.combine('case', keysOf(kFunctionCases)))
.fn(t => {
const func_case = kFunctionCases[t.params.case];
const code = `
@group(0) @binding(0)
var t : texture_2d<f32>;
@group(0) @binding(1)
var s : sampler;
@group(0) @binding(2)
var td : texture_depth_2d;
@group(0) @binding(3)
var sd : sampler_comparison;
@group(0) @binding(4)
var ta : texture_2d_array<f32>;
@group(0) @binding(5)
var ts : texture_multisampled_2d<f32>;
const uniform_cond = true;
var<private> nonuniform_cond = true;
@group(1) @binding(0)
var<storage> uniform_values : array<u32, 4>;
@group(1) @binding(1)
var<storage, read_write> nonuniform_values : array<u32, 4>;
${func_case.function}
@fragment
fn main() {
${func_case.call}
if ${func_case.cond} {
let tmp = textureSample(t,s,vec2f(0,0));
}
}
`;
const res = func_case.uniform;
if (!res) {
t.expectCompileResult(true, `diagnostic(off, derivative_uniformity);\n` + code);
}
t.expectCompileResult(res, code);
});
g.test('subgroups,parameters')
.desc('Test subgroup operations that require a uniform parameter')
.params(u =>
u
.combine('op', ['subgroupShuffleUp', 'subgroupShuffleDown', 'subgroupShuffleXor'] as const)
.combine('uniform', [false, true] as const)
)
.fn(t => {
const wgsl = `
enable subgroups;
var<private> non_uniform : u32 = 0;
@group(0) @binding(0)
var<storage> uniform : u32;
@compute @workgroup_size(16,1,1)
fn main() {
let x = ${t.params.op}(non_uniform, ${t.params.uniform ? 'uniform' : 'non_uniform'});
}`;
t.expectCompileResult(t.params.uniform, wgsl);
});