Skip to content

Commit d20b18f

Browse files
committed
Validate setImmediates API in different encoder types
This PR adding validation tests to cover setImmediates API in different encoder types (compute pass, render pass, render bundle) by covering: * Interpretation: - Passing a TypedArray the data offset and size is not given in elements. * Alignment: - rangeOffset is not a multiple of 4 bytes. - content size, converted to bytes, is not a multiple of 4 bytes. * Arithmetic overflow - rangeOffset + contentSize is overflow * Bounds: - dataOffset + size (in bytes) exceeds the content data size. - rangeOffset + size (in bytes) exceeds the maxImmdiateSize.
1 parent eca60d9 commit d20b18f

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
export const description = `
2+
setImmediates validation tests.
3+
4+
Test different encoder types (compute pass, render pass, render bundle):
5+
* Interpretation:
6+
- Passing a TypedArray the data offset and size is not given in elements.
7+
* Alignment:
8+
- rangeOffset is not a multiple of 4 bytes.
9+
- content size, converted to bytes, is not a multiple of 4 bytes.
10+
* Arithmetic overflow
11+
- rangeOffset + contentSize is overflow
12+
* Bounds:
13+
- dataOffset + size (in bytes) exceeds the content data size.
14+
- rangeOffset + size (in bytes) exceeds the maxImmediateSize.
15+
`;
16+
17+
import { makeTestGroup } from '../../../../../common/framework/test_group.js';
18+
import {
19+
kTypedArrayBufferViewConstructors,
20+
TypedArrayBufferViewConstructor,
21+
} from '../../../../../common/util/util.js';
22+
import { Float16Array } from '../../../../../external/petamoriken/float16/float16.js';
23+
import { AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js';
24+
import { kProgrammableEncoderTypes } from '../../../../util/command_buffer_maker.js';
25+
import { kMaxSafeMultipleOf8 } from '../../../../util/math.js';
26+
27+
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
28+
29+
g.test('interpretation')
30+
.desc('Tests that contentSize is interpreted as element size with TypedArray.')
31+
.paramsSubcasesOnly(u =>
32+
u //
33+
.combine('encoderType', kProgrammableEncoderTypes)
34+
.combine('success', [true, false])
35+
)
36+
.fn(t => {
37+
const { encoderType, success } = t.params;
38+
39+
function runTest(arrayBufferType: TypedArrayBufferViewConstructor) {
40+
const kMinAlignmentBytes = 4;
41+
const elementSize = arrayBufferType.BYTES_PER_ELEMENT;
42+
const maxImmediateSize = t.device.limits.maxImmediateSize; // using this limit for tests
43+
const validDataSize = success
44+
? Math.floor((maxImmediateSize - kMinAlignmentBytes) / elementSize)
45+
: maxImmediateSize - kMinAlignmentBytes;
46+
const validOffset = success
47+
? Math.ceil(kMinAlignmentBytes / elementSize)
48+
: kMinAlignmentBytes;
49+
50+
const { encoder, validateFinish } = t.createEncoder(encoderType);
51+
const data = new arrayBufferType(maxImmediateSize);
52+
53+
encoder.setImmediates(/* rangeOffset */ 0, data, /* dataOffset */ validOffset, validDataSize);
54+
55+
validateFinish(success || elementSize === 1);
56+
}
57+
58+
for (const arrayType of kTypedArrayBufferViewConstructors) {
59+
if (arrayType === Float16Array) {
60+
// Skip Float16Array since it is supplied by an external module, so there isn't an overload for it.
61+
continue;
62+
}
63+
runTest(arrayType);
64+
}
65+
});
66+
67+
g.test('alignment')
68+
.desc('Tests that rangeOffset and contentSize must align to 4 bytes.')
69+
.paramsSubcasesOnly(u =>
70+
u //
71+
.combine('encoderType', kProgrammableEncoderTypes)
72+
.combineWithParams([
73+
// control case
74+
{ offset: 4, contentByteSize: 4, _offsetValid: true, _contentValid: true },
75+
// offset is not aligned to 4 bytes
76+
{ offset: 1, contentByteSize: 4, _offsetValid: false, _contentValid: true },
77+
// contentSize is not aligned to 4 bytes
78+
{ offset: 4, contentByteSize: 5, _offsetValid: true, _contentValid: false },
79+
] as const)
80+
)
81+
.fn(t => {
82+
const { encoderType, offset, contentByteSize, _offsetValid, _contentValid } = t.params;
83+
const data = new Uint8Array(contentByteSize);
84+
85+
const { encoder, validateFinish } = t.createEncoder(encoderType);
86+
87+
const doSetImmediates = () => {
88+
encoder.setImmediates(offset, data, 0, contentByteSize);
89+
};
90+
91+
if (_contentValid) {
92+
doSetImmediates();
93+
} else {
94+
t.shouldThrow('RangeError', doSetImmediates);
95+
}
96+
97+
validateFinish(_offsetValid);
98+
});
99+
100+
g.test('overflow')
101+
.desc('Tests that rangeOffset + contentSize exceed Number.MAX_SAFE_INTEGER.')
102+
.paramsSubcasesOnly(u =>
103+
u //
104+
.combine('encoderType', kProgrammableEncoderTypes)
105+
.combineWithParams([
106+
// control case
107+
{ offset: 4, contentByteSize: 4, _rangeValid: true, _contentValid: true },
108+
// rangeOffset + contentSize is overflow
109+
{
110+
offset: 4,
111+
contentByteSize: kMaxSafeMultipleOf8,
112+
_rangeValid: true,
113+
_contentValid: false,
114+
},
115+
] as const)
116+
)
117+
.fn(t => {
118+
const { encoderType, offset, contentByteSize, _rangeValid, _contentValid } = t.params;
119+
const data = new Uint8Array(t.device.limits.maxImmediateSize);
120+
121+
const { encoder, validateFinish } = t.createEncoder(encoderType);
122+
123+
const doSetImmediates = () => {
124+
encoder.setImmediates(offset, data, 0, contentByteSize);
125+
};
126+
127+
if (_contentValid) {
128+
doSetImmediates();
129+
} else {
130+
t.shouldThrow('RangeError', doSetImmediates);
131+
}
132+
133+
validateFinish(_rangeValid);
134+
});
135+
136+
g.test('out_of_bounds')
137+
.desc(
138+
'Tests that rangeOffset + contentSize is greater than maxImmediateSize and contentSize is larger than data size.'
139+
)
140+
.paramsSubcasesOnly(u =>
141+
u //
142+
.combine('encoderType', kProgrammableEncoderTypes)
143+
.combineWithParams([
144+
// control case
145+
{
146+
rangeRemainSpace: 4,
147+
dataByteSize: 32,
148+
immediateContentByteSize: 4,
149+
_rangeValid: true,
150+
_contentValid: true,
151+
},
152+
// offset + contentByteSize larger than maxImmediateSize
153+
{
154+
rangeRemainSpace: 0,
155+
dataByteSize: 32,
156+
immediateContentByteSize: 4,
157+
_rangeValid: false,
158+
_contentValid: true,
159+
},
160+
// contentSize is larger than data size
161+
{
162+
rangeRemainSpace: 8,
163+
dataByteSize: 4,
164+
immediateContentByteSize: 8,
165+
_rangeValid: true,
166+
_contentValid: false,
167+
},
168+
] as const)
169+
)
170+
.fn(t => {
171+
const {
172+
encoderType,
173+
rangeRemainSpace,
174+
dataByteSize,
175+
immediateContentByteSize,
176+
_rangeValid,
177+
_contentValid,
178+
} = t.params;
179+
const maxImmediates = t.device.limits.maxImmediateSize;
180+
const rangeOffset = maxImmediates - rangeRemainSpace;
181+
const data = new Uint8Array(dataByteSize);
182+
183+
const { encoder, validateFinish } = t.createEncoder(encoderType);
184+
185+
const doSetImmediates = () => {
186+
encoder.setImmediates(rangeOffset, data, 0, immediateContentByteSize);
187+
};
188+
189+
if (_contentValid) {
190+
doSetImmediates();
191+
} else {
192+
t.shouldThrow('RangeError', doSetImmediates);
193+
}
194+
195+
validateFinish(_rangeValid);
196+
});

0 commit comments

Comments
 (0)