Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] main from gpuweb:main #106

Merged
merged 2 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,12 @@ export class LimitTestsImpl extends GPUTestBase {

skipIfNotEnoughStorageBuffersInStage(visibility: GPUShaderStageFlags, numRequired: number) {
const { device } = this;

this.skipIf(
numRequired > device.limits.maxStorageBuffersPerShaderStage,
`maxStorageBuffersPerShaderStage = ${device.limits.maxSamplersPerShaderStage} which is less than ${numRequired}`
);

this.skipIf(
this.isCompatibility &&
// If we're using the fragment stage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ g.test('createBindGroupLayout,at_over')
);
});

// MAINTENANCE_TODO: Update this test - this test wants to test the maxDynamicStorageBuffersPerPipelineLayout
// but to get the max it should split them between stages so that it's less likely to hit the per stage limit.
g.test('createPipelineLayout,at_over')
.desc(`Test using at and over ${limit} limit in createPipelineLayout`)
.params(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ g.test('createBindGroupLayout,at_over')
}
);
});

g.test('createPipelineLayout,at_over')
.desc(`Test using at and over ${limit} limit in createPipelineLayout`)
.unimplemented();
4 changes: 2 additions & 2 deletions src/webgpu/gpu_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1718,11 +1718,11 @@ export class AllFeaturesMaxLimitsGPUTestSubcaseBatchState extends GPUTestSubcase
* like those under api/validation/capability_checks/feature.
*
* NOTE: The goal is to go through all existing tests and remove any direct use of GPUTest.
* For each test, choose either AllFeaturesMaxLimitsGPUTest or GPUTestWithUniqueFeaturesOrLimits.
* For each test, choose either AllFeaturesMaxLimitsGPUTest or UniqueFeaturesOrLimitsGPUTest.
* This way we can track progress as we go through every test using GPUTest and check it is
* testing everything it should test.
*/
export class GPUTestWithUniqueFeaturesOrLimits extends GPUTest {}
export class UniqueFeaturesOrLimitsGPUTest extends GPUTest {}

/**
* A test that requests all features and maximum limits. This should be the default
Expand Down
34 changes: 15 additions & 19 deletions src/webgpu/shader/execution/value_init.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,10 @@ g.test('scalars')
)
.fn(async t => {
const typeDecl = t.params.type;
const testValue = Type[typeDecl].create(5).wgsl();

if (typeDecl === 'f16') {
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
}
const testValue = Type[typeDecl].create(5).wgsl();

const comparison = `if (testVar != ${testValue}) {
atomicStore(&output.failed, 1u);
Expand All @@ -117,13 +116,11 @@ g.test('vec')
.combine('count', [2, 3, 4] as const)
)
.fn(async t => {
const typeDecl = `vec${t.params.count}<${t.params.type}>`;
const testValue = `${typeDecl}(${Type[t.params.type].create(5).wgsl()})`;

if (typeDecl === 'f16') {
if (t.params.type === 'f16') {
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
}

const typeDecl = `vec${t.params.count}<${t.params.type}>`;
const testValue = `${typeDecl}(${Type[t.params.type].create(5).wgsl()})`;
const comparison = `if (!all(testVar == ${testValue})) {
atomicStore(&output.failed, 1u);
}`;
Expand Down Expand Up @@ -151,12 +148,11 @@ g.test('mat')
.combine('r', [2, 3, 4] as const)
)
.fn(async t => {
const typeDecl = `mat${t.params.c}x${t.params.r}<${t.params.type}>`;
const testScalarValue = Type[t.params.type].create(5).wgsl();

if (typeDecl === 'f16') {
if (t.params.type === 'f16') {
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
}
const typeDecl = `mat${t.params.c}x${t.params.r}<${t.params.type}>`;
const testScalarValue = Type[t.params.type].create(5).wgsl();

let testValue = `${typeDecl}(`;
for (let c = 0; c < t.params.c; c++) {
Expand Down Expand Up @@ -192,13 +188,13 @@ g.test('array')
.combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
)
.fn(async t => {
if (t.params.type === 'f16') {
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
}
const arraySize = 4;
const typeDecl = `array<${t.params.type}, ${arraySize}>`;
const testScalarValue = Type[t.params.type].create(5).wgsl();

if (typeDecl === 'f16') {
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
}
const testScalarValue = Type[t.params.type].create(5).wgsl();

let testValue = `${typeDecl}(`;
for (let i = 0; i < arraySize; i++) {
Expand Down Expand Up @@ -230,15 +226,15 @@ g.test('array,nested')
.combine('type', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
)
.fn(async t => {
if (t.params.type === 'f16') {
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
}
const arraySize = 4;

const innerDecl = `array<${t.params.type}, ${arraySize}>`;
const typeDecl = `array<${innerDecl}, ${arraySize}>`;
const testScalarValue = Type[t.params.type].create(5).wgsl();

if (typeDecl === 'f16') {
t.skipIfDeviceDoesNotHaveFeature('shader-f16');
}
const testScalarValue = Type[t.params.type].create(5).wgsl();

let testValue = `${typeDecl}(`;
for (let i = 0; i < arraySize; i++) {
Expand Down
4 changes: 2 additions & 2 deletions src/webgpu/shader/validation/extension/clip_distances.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ Validation tests for the clip_distances extension
`;

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
import { UniqueFeaturesAndLimitsShaderValidationTest } from '../shader_validation_test.js';

export const g = makeTestGroup(ShaderValidationTest);
export const g = makeTestGroup(UniqueFeaturesAndLimitsShaderValidationTest);

g.test('use_clip_distances_requires_extension_enabled')
.desc(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Validation tests for the dual_source_blending extension

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';
import { UniqueFeaturesAndLimitsShaderValidationTest } from '../shader_validation_test.js';

export const g = makeTestGroup(ShaderValidationTest);
export const g = makeTestGroup(UniqueFeaturesAndLimitsShaderValidationTest);

g.test('use_blend_src_requires_extension_enabled')
.desc(
Expand Down
185 changes: 184 additions & 1 deletion src/webgpu/shader/validation/shader_validation_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { keysOf } from '../../../common/util/data_tables.js';
import { ErrorWithExtra } from '../../../common/util/util.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../gpu_test.js';
import { AllFeaturesMaxLimitsGPUTest, UniqueFeaturesOrLimitsGPUTest } from '../../gpu_test.js';

/**
* Base fixture for WGSL shader validation tests.
Expand Down Expand Up @@ -183,3 +183,186 @@ fn main() {
}`;
}
}

// MAINTENANCE_TODO: Merge this with implementation above.
// NOTE: These things should probably not inherit. There is no relationship between
// these functions and a test. They could just as easily take a GPUTest as the first
// argument and then the all the problems associated with inheritance would disappear.
export class UniqueFeaturesAndLimitsShaderValidationTest extends UniqueFeaturesOrLimitsGPUTest {
/**
* Add a test expectation for whether a createShaderModule call succeeds or not.
*
* @example
* ```ts
* t.expectCompileResult(true, `wgsl code`); // Expect success
* t.expectCompileResult(false, `wgsl code`); // Expect validation error with any error string
* ```
*/
expectCompileResult(expectedResult: boolean, code: string) {
let shaderModule: GPUShaderModule;
this.expectGPUError(
'validation',
() => {
shaderModule = this.device.createShaderModule({ code });
},
expectedResult !== true
);

const error = new ErrorWithExtra('', () => ({ shaderModule }));
this.eventualAsyncExpectation(async () => {
const compilationInfo = await shaderModule!.getCompilationInfo();

// MAINTENANCE_TODO: Pretty-print error messages with source context.
const messagesLog =
compilationInfo.messages
.map(m => `${m.lineNum}:${m.linePos}: ${m.type}: ${m.message}`)
.join('\n') +
'\n\n---- shader ----\n' +
code;
error.extra.compilationInfo = compilationInfo;

if (compilationInfo.messages.some(m => m.type === 'error')) {
if (expectedResult) {
error.message = `Unexpected compilationInfo 'error' message.\n` + messagesLog;
this.rec.validationFailed(error);
} else {
error.message = `Found expected compilationInfo 'error' message.\n` + messagesLog;
this.rec.debug(error);
}
} else {
if (!expectedResult) {
error.message = `Missing expected compilationInfo 'error' message.\n` + messagesLog;
this.rec.validationFailed(error);
} else {
error.message = `No compilationInfo 'error' messages, as expected.\n` + messagesLog;
this.rec.debug(error);
}
}
});
}

/**
* Add a test expectation for whether a createShaderModule call issues a warning.
*
* @example
* ```ts
* t.expectCompileWarning(true, `wgsl code`); // Expect compile success and any warning message
* t.expectCompileWarning(false, `wgsl code`); // Expect compile success and no warning messages
* ```
*/
expectCompileWarning(expectWarning: boolean, code: string) {
let shaderModule: GPUShaderModule;
this.expectGPUError(
'validation',
() => {
shaderModule = this.device.createShaderModule({ code });
},
false
);

const error = new ErrorWithExtra('', () => ({ shaderModule }));
this.eventualAsyncExpectation(async () => {
const compilationInfo = await shaderModule!.getCompilationInfo();

// MAINTENANCE_TODO: Pretty-print error messages with source context.
const messagesLog = compilationInfo.messages
.map(m => `${m.lineNum}:${m.linePos}: ${m.type}: ${m.message}`)
.join('\n');
error.extra.compilationInfo = compilationInfo;

if (compilationInfo.messages.some(m => m.type === 'warning')) {
if (expectWarning) {
error.message = `No 'warning' message as expected.\n` + messagesLog;
this.rec.debug(error);
} else {
error.message = `Missing expected compilationInfo 'warning' message.\n` + messagesLog;
this.rec.validationFailed(error);
}
} else {
if (expectWarning) {
error.message = `Missing expected 'warning' message.\n` + messagesLog;
this.rec.validationFailed(error);
} else {
error.message = `Found a 'warning' message as expected.\n` + messagesLog;
this.rec.debug(error);
}
}
});
}

/**
* Add a test expectation for whether a createComputePipeline call succeeds or not.
*/
expectPipelineResult(args: {
// True if the pipeline should build without error
expectedResult: boolean;
// The WGSL shader code
code: string;
// Pipeline overridable constants
constants?: Record<string, GPUPipelineConstantValue>;
// List of additional module-scope variable the entrypoint needs to reference
reference?: string[];
// List of additional statements to insert in the entry point.
statements?: string[];
}) {
const phonies: Array<string> = [];

if (args.statements !== undefined) {
phonies.push(...args.statements);
}
if (args.constants !== undefined) {
phonies.push(...keysOf(args.constants).map(c => `_ = ${c};`));
}
if (args.reference !== undefined) {
phonies.push(...args.reference.map(c => `_ = ${c};`));
}

const code =
args.code +
`
@compute @workgroup_size(1)
fn main() {
${phonies.join('\n')}
}`;

let shaderModule: GPUShaderModule;
this.expectGPUError(
'validation',
() => {
shaderModule = this.device.createShaderModule({ code });
},
false
);

this.expectGPUError(
'validation',
() => {
this.device.createComputePipeline({
layout: 'auto',
compute: { module: shaderModule!, entryPoint: 'main', constants: args.constants },
});
},
!args.expectedResult
);
}

/**
* Wraps the code fragment into an entry point.
*
* @example
* ```ts
* t.wrapInEntryPoint(`var i = 0;`);
* ```
*/
wrapInEntryPoint(code: string, enabledExtensions: string[] = []) {
const enableDirectives = enabledExtensions.map(x => `enable ${x};`).join('\n ');

return `
${enableDirectives}

@compute @workgroup_size(1)
fn main() {
${code}
}`;
}
}