Skip to content
Open
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
Binary file modified src/resources/cache/webgpu/shader/execution/bitcast.bin
Binary file not shown.
73 changes: 73 additions & 0 deletions src/webgpu/api/validation/compute_pipeline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Note: entry point matching tests are in shader_module/entry_point.spec.ts
import { AllFeaturesMaxLimitsGPUTest } from '../.././gpu_test.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { keysOf } from '../../../common/util/data_tables.js';
import { getGPU } from '../../../common/util/navigator_gpu.js';
import { supportsImmediateData } from '../../../common/util/util.js';
import {
isTextureFormatUsableWithStorageAccessMode,
kPossibleStorageTextureFormats,
Expand Down Expand Up @@ -811,3 +813,74 @@ generates a validation error at createComputePipeline(Async)
};
vtu.doCreateComputePipelineTest(t, isAsync, success, descriptor);
});

g.test('pipeline_creation_immediate_size_mismatch')
.desc(
`
Validate that creating a pipeline fails if the shader uses immediate data
larger than the immediateSize specified in the pipeline layout, or larger than
maxImmediateSize if layout is 'auto'.
Also validates that using less or equal size is allowed.
`
)
.params(u => {
const kNumericCases = [
{ shaderSize: 16, layoutSize: 16 }, // Equal
{ shaderSize: 12, layoutSize: 16 }, // Shader smaller
{ shaderSize: 20, layoutSize: 16 }, // Shader larger (small diff)
{ shaderSize: 32, layoutSize: 16 }, // Shader larger
] as const;
Comment on lines +827 to +832
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These numeric cases create a pipeline layout with immediateSize: 16 without checking that the device supports at least 16 bytes (maxImmediateSize >= 16). If maxImmediateSize is smaller, the test will fail for the wrong reason and the control cases won't be meaningful. Consider filtering/skipping when maxImmediateSize < 16 (or choosing sizes relative to the limit).

Copilot uses AI. Check for mistakes.
const kMaxLimitsCases = [
{ shaderSize: 'max', layoutSize: 'auto' }, // Shader equal to limit (auto layout)
{ shaderSize: 'max+4', layoutSize: 'auto' }, // Shader larger than limit (auto layout)
] as const;
return u
.combine('isAsync', [true, false])
.combineWithParams([...kNumericCases, ...kMaxLimitsCases] as const);
})
.fn(t => {
t.skipIf(!supportsImmediateData(getGPU(t.rec)), 'Immediate data not supported');

const { isAsync, shaderSize, layoutSize } = t.params;

const maxImmediateSize = t.device.limits.maxImmediateSize!;

Comment on lines +842 to +847
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supportsImmediateData() does not guarantee device.limits.maxImmediateSize is defined (some tests still skip when it's missing). Here maxImmediateSize is non-null asserted and then used to compute actualShaderSize and generate WGSL; if it's undefined you can end up with invalid WGSL (array size NaN) and misleading failures. Add a guard to skip when maxImmediateSize is undefined before using it.

Copilot uses AI. Check for mistakes.
let actualLayout: GPUPipelineLayout | 'auto';
let validSize: number;

if (layoutSize === 'auto') {
actualLayout = 'auto';
// checked above
validSize = maxImmediateSize!;
} else {
actualLayout = t.device.createPipelineLayout({
bindGroupLayouts: [],
immediateSize: layoutSize as number,
});
validSize = layoutSize as number;
}

let actualShaderSize: number;
if (shaderSize === 'max') {
actualShaderSize = validSize;
} else if (shaderSize === 'max+4') {
actualShaderSize = validSize + 4;
} else {
actualShaderSize = shaderSize as number;
}

const code = `
var<immediate> data: array<u32, ${actualShaderSize / 4}>;
fn use() { _ = data[0]; }
@compute @workgroup_size(1) fn main_compute() { use(); }
`;

const shouldError = actualShaderSize > validSize;

vtu.doCreateComputePipelineTest(t, isAsync, !shouldError, {
layout: actualLayout,
compute: {
module: t.device.createShaderModule({ code }),
},
});
});
89 changes: 87 additions & 2 deletions src/webgpu/api/validation/render_pipeline/misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ misc createRenderPipeline and createRenderPipelineAsync validation tests.
`;

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { getGPU } from '../../../../common/util/navigator_gpu.js';
import { supportsImmediateData } from '../../../../common/util/util.js';
import {
isTextureFormatUsableWithStorageAccessMode,
kPossibleStorageTextureFormats,
Expand Down Expand Up @@ -119,8 +121,10 @@ g.test('pipeline_layout,device_mismatch')
});

g.test('external_texture')
.desc('Tests createRenderPipeline() with an external_texture')
.desc('Tests createRenderPipeline(Async) with an external_texture')
.params(u => u.combine('isAsync', [false, true]))
.fn(t => {
const { isAsync } = t.params;
const shader = t.device.createShaderModule({
code: `
@vertex
Expand Down Expand Up @@ -149,7 +153,7 @@ g.test('external_texture')
},
};

vtu.doCreateRenderPipelineTest(t, false, true, descriptor);
vtu.doCreateRenderPipelineTest(t, isAsync, true, descriptor);
});

g.test('storage_texture,format')
Expand Down Expand Up @@ -192,3 +196,84 @@ generates a validation error at createComputePipeline(Async)
};
vtu.doCreateRenderPipelineTest(t, isAsync, success, descriptor);
});

g.test('pipeline_creation_immediate_size_mismatch')
.desc(
`
Validate that creating a pipeline fails if the shader uses immediate data
larger than the immediateSize specified in the pipeline layout, or larger than
maxImmediateSize if layout is 'auto'.
Also validates that using less or equal size is allowed.
`
)
.params(u => {
const kNumericCases = [
{ vertexSize: 16, fragmentSize: 16, layoutSize: 16 }, // Equal
{ vertexSize: 12, fragmentSize: 12, layoutSize: 16 }, // Shader smaller
{ vertexSize: 20, fragmentSize: 20, layoutSize: 16 }, // Shader larger (small diff)
{ vertexSize: 32, fragmentSize: 32, layoutSize: 16 }, // Shader larger
] as const;
Comment on lines +210 to +215
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The numeric cases hardcode layoutSize = 16 and assume creating a pipeline layout with immediateSize: 16 is valid. On implementations with maxImmediateSize < 16, createPipelineLayout() (or pipeline creation) will fail for reasons unrelated to the size-mismatch being tested, breaking the control cases. Consider skipping the test (or filtering those params) when maxImmediateSize < 16, similar to other immediate-data tests that compare against device limits.

Copilot uses AI. Check for mistakes.
const kMaxLimitsCases = [
{ vertexSize: 'max', fragmentSize: 0, layoutSize: 'auto' }, // Vertex = Limit (Control)
{ vertexSize: 0, fragmentSize: 'max', layoutSize: 'auto' }, // Fragment = Limit (Control)
{ vertexSize: 'max', fragmentSize: 'max', layoutSize: 'auto' }, // Both at Limit (Control)
{ vertexSize: 'max+4', fragmentSize: 0, layoutSize: 'auto' }, // Vertex > Limit
{ vertexSize: 0, fragmentSize: 'max+4', layoutSize: 'auto' }, // Fragment > Limit
] as const;
return u
.combine('isAsync', [true, false])
.combineWithParams([...kNumericCases, ...kMaxLimitsCases] as const);
})
.fn(t => {
t.skipIf(!supportsImmediateData(getGPU(t.rec)), 'Immediate data not supported');

const { isAsync, vertexSize, fragmentSize, layoutSize } = t.params;

const maxImmediateSize = t.device.limits.maxImmediateSize!;
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supportsImmediateData() can be true even when device.limits.maxImmediateSize is undefined (see other immediate-data tests that explicitly skip in that case). This test uses maxImmediateSize with a non-null assertion and interpolates it into WGSL; if it's undefined you'll end up generating invalid WGSL (e.g. array size NaN) and the failure won't reflect the intended validation. Add a guard to skip when maxImmediateSize is undefined before using it.

Suggested change
const maxImmediateSize = t.device.limits.maxImmediateSize!;
const maxImmediateSize = t.device.limits.maxImmediateSize;
if (maxImmediateSize === undefined) {
t.skip('Device limit maxImmediateSize is undefined');
}

Copilot uses AI. Check for mistakes.

const resolveSize = (sizeDescriptor: number | string) => {
if (typeof sizeDescriptor === 'number') return sizeDescriptor;
if (sizeDescriptor === 'max') return maxImmediateSize;
if (sizeDescriptor === 'max+4') return maxImmediateSize + 4;
return 0;
};

const resolvedVertexImmediateSize = resolveSize(vertexSize);
const resolvedFragmentImmediateSize = resolveSize(fragmentSize);
const varSize = Math.max(resolvedVertexImmediateSize, resolvedFragmentImmediateSize);

const code = `
var<immediate> data: array<u32, ${varSize / 4}>;
fn use_v() { ${resolvedVertexImmediateSize > 0 ? '_ = data[0];' : ''} }
fn use_f() { ${resolvedFragmentImmediateSize > 0 ? '_ = data[0];' : ''} }
@vertex fn main_vertex() -> @builtin(position) vec4<f32> { use_v(); return vec4<f32>(0.0, 0.0, 0.0, 1.0); }
@fragment fn main_fragment() -> @location(0) vec4<f32> { use_f(); return vec4<f32>(0.0, 1.0, 0.0, 1.0); }
`;

let layout: GPUPipelineLayout | 'auto';
let validSize: number;

if (layoutSize === 'auto') {
layout = 'auto';
validSize = maxImmediateSize;
} else {
layout = t.device.createPipelineLayout({
bindGroupLayouts: [],
immediateSize: layoutSize as number,
});
validSize = layoutSize as number;
}

const shouldError = varSize > validSize;

vtu.doCreateRenderPipelineTest(t, isAsync, !shouldError, {
layout,
vertex: {
module: t.device.createShaderModule({ code }),
},
fragment: {
module: t.device.createShaderModule({ code }),
targets: [{ format: 'rgba8unorm' }],
},
});
});
Loading