Skip to content

Commit

Permalink
Compat: Test texture+sampler limits are enforced
Browse files Browse the repository at this point in the history
  • Loading branch information
greggman committed Nov 27, 2024
1 parent a546ae2 commit 53f88ae
Showing 1 changed file with 155 additions and 0 deletions.
155 changes: 155 additions & 0 deletions src/webgpu/compat/api/validation/pipeline_creation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,158 @@ g.test('depth_textures')
break;
}
});

function numCombosToNumber(maxCombos: number, numCombos: '1' | '2' | 'max' | 'max+1') {
switch (numCombos) {
case '1':
return 1;
case '2':
return 2;
case 'max':
return maxCombos;
case 'max+1':
return maxCombos + 1;
}
}

function numNonSampledToNumber(maxTextures: number, numNonSampled: '1' | 'max') {
switch (numNonSampled) {
case '1':
return 1;
case 'max':
return maxTextures;
}
}

g.test('texture_sampler_combos')
.desc(
`
Test that you can not use more texture+sampler combos than
min(maxSamplersPerShaderStage, maxSampledTexturesPerShaderStage)
in compatibility mode.
`
)
.params(u =>
u
.combineWithParams([
{ pass: true, stages: 'vertex', numCombos: 'max', numNonSampled: '1' },
{ pass: true, stages: 'fragment', numCombos: 'max', numNonSampled: '1' },
{ pass: false, stages: 'vertex', numCombos: 'max+1', numNonSampled: '1' },
{ pass: false, stages: 'fragment', numCombos: 'max+1', numNonSampled: '1' },
{ pass: true, stages: 'vertex', numCombos: '1', numNonSampled: 'max' },
{ pass: true, stages: 'fragment', numCombos: '1', numNonSampled: 'max' },
{ pass: false, stages: 'vertex', numCombos: '2', numNonSampled: 'max' },
{ pass: false, stages: 'fragment', numCombos: '2', numNonSampled: 'max' },
{ pass: true, stages: 'vertex,fragment', numCombos: 'max', numNonSampled: '1' },
] as const)
.combine('async', [false, true] as const)
)
.fn(t => {
const { device } = t;
const { pass, stages, numCombos, numNonSampled, async } = t.params;
const { maxSampledTexturesPerShaderStage, maxSamplersPerShaderStage } = device.limits;

const maxCombos = Math.min(maxSampledTexturesPerShaderStage, maxSamplersPerShaderStage);
const numCombinations = numCombosToNumber(maxCombos, numCombos);
const numNonSampledTextures = numNonSampledToNumber(
maxSampledTexturesPerShaderStage,
numNonSampled
);

const textureDeclarations: string[][] = [[], []];
const samplerDeclarations: string[][] = [[], []];
const usages: string[][] = [[], []];
const bindGroupLayoutEntries: GPUBindGroupLayoutEntry[][] = [[], [], [], []];
const visibilityByStage = [GPUShaderStage.VERTEX, GPUShaderStage.FRAGMENT];

const addTextureDeclaration = (stage: number, binding: number) => {
textureDeclarations[stage].push(
`@group(${stage * 2}) @binding(${binding}) var t${stage}_${binding}: texture_2d<f32>;`
);
bindGroupLayoutEntries[stage * 2].push({
binding,
visibility: visibilityByStage[stage],
texture: {},
});
};

const addSamplerDeclaration = (stage: number, binding: number) => {
samplerDeclarations[stage].push(
`@group(${stage * 2 + 1}) @binding(${binding}) var s${stage}_${binding}: sampler;`
);
bindGroupLayoutEntries[stage * 2 + 1].push({
binding,
visibility: visibilityByStage[stage],
sampler: {},
});
};

for (let stage = 0; stage < 2; ++stage) {
let count = 0;
for (let t = 0; count < numCombinations && t < maxSampledTexturesPerShaderStage; ++t) {
addTextureDeclaration(stage, t);
for (let s = 0; count < numCombinations && s < maxSamplersPerShaderStage; ++s) {
if (t === 0) {
addSamplerDeclaration(stage, s);
}
usages[stage].push(
` c += textureSampleLevel(t${stage}_${t}, s${stage}_${s}, vec2f(0), 0);`
);
++count;
}
}

for (let t = 0; t < numNonSampledTextures; ++t) {
if (t >= textureDeclarations[stage].length) {
addTextureDeclaration(stage, t);
}
usages[stage].push(` c += textureLoad(t${stage}_${t}, vec2u(0), 0);`);
}
}

const code = `
${textureDeclarations[0].join('\n')}
${textureDeclarations[1].join('\n')}
${samplerDeclarations[0].join('\n')}
${samplerDeclarations[1].join('\n')}
fn usage0() -> vec4f {
var c: vec4f;
${usages[0].join('\n')}
return c;
}
fn usage1() -> vec4f {
var c: vec4f;
${usages[1].join('\n')}
return c;
}
@vertex fn vs() -> @builtin(position) vec4f {
_ = ${stages.includes('vertex') ? 'usage0()' : 'vec4f(0)'};
return vec4f(0);
}
@group(2) @binding(0) var tt: texture_2d<f32>;
@fragment fn fs() -> @location(0) vec4f {
return ${stages.includes('fragment') ? 'usage1()' : 'vec4f(0)'};
}
`;

// MAINTENANCE_TODO: remove this. It's only needed because of a bug in dawn
// with auto layouts.
const layout = device.createPipelineLayout({
bindGroupLayouts: bindGroupLayoutEntries.map(entries =>
device.createBindGroupLayout({ entries })
),
});

const module = device.createShaderModule({ code });
t.doCreateRenderPipelineTest(async, pass || !t.isCompatibility, {
layout,
vertex: { module },
fragment: { module, targets: [{ format: 'rgba8unorm' }] },
});
});

0 comments on commit 53f88ae

Please sign in to comment.