-
Notifications
You must be signed in to change notification settings - Fork 100
Validate setImmediates API in different encoder types #4515
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
base: main
Are you sure you want to change the base?
Changes from all commits
d20b18f
c2d4647
76323b7
ea4de3e
3632ea1
2b0f03b
744fdea
ee76700
4c81edc
f3118e7
f3434c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| export const description = ` | ||
| setImmediates validation tests. | ||
| `; | ||
|
|
||
| import { makeTestGroup } from '../../../../../common/framework/test_group.js'; | ||
| import { | ||
| kTypedArrayBufferViews, | ||
| kTypedArrayBufferViewKeys, | ||
| } from '../../../../../common/util/util.js'; | ||
| import { AllFeaturesMaxLimitsGPUTest } from '../../../../gpu_test.js'; | ||
| import { kProgrammableEncoderTypes } from '../../../../util/command_buffer_maker.js'; | ||
|
|
||
| class SetImmediatesTest extends AllFeaturesMaxLimitsGPUTest { | ||
| override async init() { | ||
| await super.init(); | ||
| if ( | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| !('setImmediates' in (GPURenderPassEncoder.prototype as any)) || | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| !('setImmediates' in (GPUComputePassEncoder.prototype as any)) || | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| !('setImmediates' in (GPURenderBundleEncoder.prototype as any)) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will fully review tomorrow but one comment. This will run the tests only if all three are available. I would like to run all of the tests if ANY of these, OR the WGSL feature OR the new limit, are available. Also these checks need to be packaged up into a "skipIf" helper at some point, so they can also be used in other tests that don't use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A bit concern about this. I think it faill the scenario that implementation is in middle. For example, when implementation added wgslLanguageFeature but not do API implementation, the case failed. So my thoughts is that the test should check what it tends to check. For example, for this test case, we want to check all encoder API validation, then we need to check the API is available.(Maybe we can do more detail checking for each encoder). And for other case which test immediate state, it requires both API and wgslFeature. WDYT? |
||
| ) { | ||
| this.skip('setImmediates not supported'); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export const g = makeTestGroup(SetImmediatesTest); | ||
|
|
||
| g.test('alignment') | ||
| .desc('Tests that rangeOffset and contentSize must align to 4 bytes.') | ||
| .params(u => | ||
| u // | ||
| .combine('encoderType', kProgrammableEncoderTypes) | ||
| .combine('arrayType', kTypedArrayBufferViewKeys) | ||
| .filter(p => p.arrayType !== 'Float16Array') | ||
| .combineWithParams([ | ||
| // control case: rangeOffset 4 is aligned. contentByteSize 8 is aligned. | ||
| { rangeOffset: 4, contentByteSize: 8 }, | ||
| // rangeOffset 5 is unaligned (5 % 4 !== 0). | ||
| { rangeOffset: 5, contentByteSize: 8 }, | ||
| // contentByteSize 10 is unaligned (10 % 4 !== 0). | ||
| // Note: This case will be skipped for types with element size > 2 (e.g. Uint32, Uint64) | ||
| // because they cannot form a 10-byte array. | ||
| { rangeOffset: 4, contentByteSize: 10 }, | ||
| ]) | ||
| .filter(({ arrayType, contentByteSize }) => { | ||
| // Skip if the contentByteSize is not a multiple of the element size. | ||
| // For example, we can't have 10 bytes if the element size is 4 or 8 bytes. | ||
| const arrayConstructor = kTypedArrayBufferViews[arrayType]; | ||
| return contentByteSize % arrayConstructor.BYTES_PER_ELEMENT === 0; | ||
| }) | ||
| ) | ||
| .fn(t => { | ||
| const { encoderType, arrayType, rangeOffset, contentByteSize } = t.params; | ||
| const arrayBufferType = kTypedArrayBufferViews[arrayType]; | ||
| const elementSize = arrayBufferType.BYTES_PER_ELEMENT; | ||
| const elementCount = contentByteSize / elementSize; | ||
|
|
||
| const isRangeOffsetAligned = rangeOffset % 4 === 0; | ||
| const isContentSizeAligned = contentByteSize % 4 === 0; | ||
|
|
||
| const { encoder, validateFinish } = t.createEncoder(encoderType); | ||
| const data = new arrayBufferType(elementCount); | ||
|
|
||
| t.shouldThrow(isContentSizeAligned ? false : 'RangeError', () => { | ||
| // Cast to any to avoid Float16Array issues | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| (encoder as any).setImmediates(rangeOffset, data as any, 0, elementCount); | ||
| }); | ||
|
|
||
| validateFinish(isRangeOffsetAligned); | ||
| }); | ||
|
|
||
| g.test('overflow') | ||
| .desc( | ||
| ` | ||
| Tests that rangeOffset + contentSize or dataOffset + size is handled correctly if it exceeds limits. | ||
| ` | ||
| ) | ||
| .params(u => | ||
| u // | ||
| .combine('encoderType', kProgrammableEncoderTypes) | ||
| .combine('arrayType', kTypedArrayBufferViewKeys) | ||
| .filter(p => p.arrayType !== 'Float16Array') | ||
| .combineWithParams([ | ||
| // control case | ||
| { rangeOffset: 0, dataOffset: 0, elementCount: 4, _expectedError: null }, | ||
| // rangeOffset + contentSize overflows | ||
| { | ||
| rangeOffset: Math.pow(2, 31) - 8, | ||
| dataOffset: 0, | ||
| elementCount: 4, | ||
| _expectedError: 'validation', | ||
| }, | ||
| { | ||
| rangeOffset: Math.pow(2, 32) - 8, | ||
| dataOffset: 0, | ||
| elementCount: 4, | ||
| _expectedError: 'validation', | ||
| }, | ||
| // dataOffset + size overflows | ||
| { | ||
| rangeOffset: 0, | ||
| dataOffset: Math.pow(2, 31) - 1, | ||
| elementCount: 4, | ||
| _expectedError: 'RangeError', | ||
| }, | ||
| { | ||
| rangeOffset: 0, | ||
| dataOffset: Math.pow(2, 32) - 1, | ||
| elementCount: 4, | ||
| _expectedError: 'RangeError', | ||
| }, | ||
| ]) | ||
| ) | ||
| .fn(t => { | ||
| const { encoderType, arrayType, rangeOffset, dataOffset, elementCount, _expectedError } = | ||
| t.params; | ||
| const arrayBufferType = kTypedArrayBufferViews[arrayType]; | ||
|
|
||
| const { encoder, validateFinish } = t.createEncoder(encoderType); | ||
| const data = new arrayBufferType(elementCount); | ||
|
|
||
| const doSetImmediates = () => { | ||
| // Cast to any to avoid Float16Array issues | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| (encoder as any).setImmediates(rangeOffset, data as any, dataOffset, elementCount); | ||
| }; | ||
|
|
||
| if (_expectedError === 'RangeError') { | ||
| t.shouldThrow('RangeError', doSetImmediates); | ||
| } else { | ||
| doSetImmediates(); | ||
| validateFinish(_expectedError === null); | ||
| } | ||
| }); | ||
|
|
||
| g.test('out_of_bounds') | ||
| .desc( | ||
| ` | ||
| Tests that rangeOffset + contentSize is greater than maxImmediateSize (Validation Error) | ||
| and contentSize is larger than data size (RangeError). | ||
| ` | ||
| ) | ||
| .params(u => | ||
| u // | ||
| .combine('encoderType', kProgrammableEncoderTypes) | ||
| .combine('arrayType', kTypedArrayBufferViewKeys) | ||
| .filter(p => p.arrayType !== 'Float16Array') | ||
| .combineWithParams([ | ||
| // control case | ||
| { rangeOffsetDelta: 0, dataLengthDelta: 0 }, | ||
| // rangeOffset + contentSize > maxImmediateSize | ||
| { rangeOffsetDelta: 4, dataLengthDelta: 0 }, | ||
| // dataOffset + size > data.length | ||
| { rangeOffsetDelta: 0, dataLengthDelta: -1 }, | ||
| ]) | ||
| ) | ||
| .fn(t => { | ||
| const { encoderType, arrayType, rangeOffsetDelta, dataLengthDelta } = t.params; | ||
| const arrayBufferType = kTypedArrayBufferViews[arrayType]; | ||
| const elementSize = arrayBufferType.BYTES_PER_ELEMENT; | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const maxImmediateSize = (t.device.limits as any).maxImmediateSize; | ||
| if (maxImmediateSize === undefined) { | ||
| t.skip('maxImmediateSize not found'); | ||
| } | ||
|
|
||
| // We want contentByteSize to be aligned to 4 bytes to avoid alignment errors. | ||
| const elementCount = elementSize >= 4 ? 1 : 4 / elementSize; | ||
| const contentByteSize = elementCount * elementSize; | ||
|
|
||
| const rangeOffset = maxImmediateSize - contentByteSize + rangeOffsetDelta; | ||
| const dataLength = elementCount + dataLengthDelta; | ||
|
|
||
| const data = new arrayBufferType(dataLength); | ||
|
|
||
| const { encoder, validateFinish } = t.createEncoder(encoderType); | ||
|
|
||
| const rangeOverLimit = rangeOffset + contentByteSize > maxImmediateSize; | ||
| const dataOverLimit = elementCount > dataLength; | ||
|
|
||
| t.shouldThrow(dataOverLimit ? 'RangeError' : false, () => { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| (encoder as any).setImmediates(rangeOffset, data as any, 0, elementCount); | ||
| }); | ||
|
|
||
| if (!dataOverLimit) { | ||
| validateFinish(!rangeOverLimit); | ||
| } | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.