diff options
Diffstat (limited to 'testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js')
-rw-r--r-- | testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js | 234 |
1 files changed, 200 insertions, 34 deletions
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js index 2b589b9316..0354abdbe7 100644 --- a/testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js +++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/gpu_test.js @@ -9,6 +9,7 @@ '../common/framework/fixture.js'; import { globalTestConfig } from '../common/framework/test_config.js'; +import { getGPU } from '../common/util/navigator_gpu.js'; import { assert, makeValueTestVariant, @@ -20,15 +21,22 @@ import { unreachable } from '../common/util/util.js'; -import { getDefaultLimits, kQueryTypeInfo } from './capability_info.js'; +import { + getDefaultLimits, + + kQueryTypeInfo } from + +'./capability_info.js'; + import { kTextureFormatInfo, kEncodableTextureFormats, resolvePerAspectFormat, - isCompressedTextureFormat } from + isCompressedTextureFormat, + isTextureFormatUsableAsStorageFormat } from './format_info.js'; import { makeBufferWithContents } from './util/buffer.js'; import { checkElementsEqual, checkElementsBetween } from './util/check_contents.js'; @@ -52,7 +60,7 @@ import { textureContentIsOKByT2B } from './util/texture/texture_ok.js'; import { createTextureFromTexelView, createTextureFromTexelViews } from './util/texture.js'; -import { reifyOrigin3D } from './util/unions.js'; +import { reifyExtent3D, reifyOrigin3D } from './util/unions.js'; const devicePool = new DevicePool(); @@ -245,6 +253,56 @@ export class GPUTestSubcaseBatchState extends SubcaseBatchState { } } } + + skipIfTextureFormatNotUsableAsStorageTexture(...formats) { + for (const format of formats) { + if (format && !isTextureFormatUsableAsStorageFormat(format, this.isCompatibility)) { + this.skip(`Texture with ${format} is not usable as a storage texture`); + } + } + } + + /** + * Skips test if the given interpolation type or sampling is not supported. + */ + skipIfInterpolationTypeOrSamplingNotSupported({ + type, + sampling + + + + }) { + if (this.isCompatibility) { + this.skipIf( + type === 'linear', + 'interpolation type linear is not supported in compatibility mode' + ); + this.skipIf( + sampling === 'sample', + 'interpolation type linear is not supported in compatibility mode' + ); + } + } + + /** Skips this test case if the `langFeature` is *not* supported. */ + skipIfLanguageFeatureNotSupported(langFeature) { + if (!this.hasLanguageFeature(langFeature)) { + this.skip(`WGSL language feature '${langFeature}' is not supported`); + } + } + + /** Skips this test case if the `langFeature` is supported. */ + skipIfLanguageFeatureSupported(langFeature) { + if (this.hasLanguageFeature(langFeature)) { + this.skip(`WGSL language feature '${langFeature}' is supported`); + } + } + + /** returns true iff the `langFeature` is supported */ + hasLanguageFeature(langFeature) { + const lf = getGPU(this.recorder).wgslLanguageFeatures; + return lf !== undefined && lf.has(langFeature); + } } /** @@ -420,6 +478,26 @@ export class GPUTestBase extends Fixture { } } + /** Skips this test case if the `langFeature` is *not* supported. */ + skipIfLanguageFeatureNotSupported(langFeature) { + if (!this.hasLanguageFeature(langFeature)) { + this.skip(`WGSL language feature '${langFeature}' is not supported`); + } + } + + /** Skips this test case if the `langFeature` is supported. */ + skipIfLanguageFeatureSupported(langFeature) { + if (this.hasLanguageFeature(langFeature)) { + this.skip(`WGSL language feature '${langFeature}' is supported`); + } + } + + /** returns true iff the `langFeature` is supported */ + hasLanguageFeature(langFeature) { + const lf = getGPU(this.rec).wgslLanguageFeatures; + return lf !== undefined && lf.has(langFeature); + } + /** * Expect a GPUBuffer's contents to pass the provided check. * @@ -730,7 +808,8 @@ export class GPUTestBase extends Fixture { slice = 0, layout, generateWarningOnly = false, - checkElementsBetweenFn = (act, [a, b]) => checkElementsBetween(act, [(i) => a[i], (i) => b[i]]) + checkElementsBetweenFn = (act, [a, b]) => + checkElementsBetween(act, [(i) => a[i], (i) => b[i]]) @@ -757,24 +836,32 @@ export class GPUTestBase extends Fixture { /** * Emulate a texture to buffer copy by using a compute shader - * to load texture value of a single pixel and write to a storage buffer. - * For sample count == 1, the buffer contains only one value of the sample. - * For sample count > 1, the buffer contains (N = sampleCount) values sorted + * to load texture values of a subregion of a 2d texture and write to a storage buffer. + * For sample count == 1, the buffer contains extent[0] * extent[1] of the sample. + * For sample count > 1, the buffer contains extent[0] * extent[1] * (N = sampleCount) values sorted * in the order of their sample index [0, sampleCount - 1] * * This can be useful when the texture to buffer copy is not available to the texture format * e.g. (depth24plus), or when the texture is multisampled. * - * MAINTENANCE_TODO: extend to read multiple pixels with given origin and size. + * MAINTENANCE_TODO: extend texture dimension to 1d and 3d. * * @returns storage buffer containing the copied value from the texture. */ - copySinglePixelTextureToBufferUsingComputePass( + copy2DTextureToBufferUsingComputePass( type, componentCount, textureView, - sampleCount) + sampleCount = 1, + extent_ = [1, 1, 1], + origin_ = [0, 0, 0]) { + const origin = reifyOrigin3D(origin_); + const extent = reifyExtent3D(extent_); + const width = extent.width; + const height = extent.height; + const kWorkgroupSizeX = 8; + const kWorkgroupSizeY = 8; const textureSrcCode = sampleCount === 1 ? `@group(0) @binding(0) var src: texture_2d<${type}>;` : @@ -787,13 +874,24 @@ export class GPUTestBase extends Fixture { ${textureSrcCode} @group(0) @binding(1) var<storage, read_write> dst : Buffer; - @compute @workgroup_size(1) fn main() { - var coord = vec2<i32>(0, 0); - for (var sampleIndex = 0; sampleIndex < ${sampleCount}; + struct Params { + origin: vec2u, + extent: vec2u, + }; + @group(0) @binding(2) var<uniform> params : Params; + + @compute @workgroup_size(${kWorkgroupSizeX}, ${kWorkgroupSizeY}, 1) fn main(@builtin(global_invocation_id) id : vec3u) { + let boundary = params.origin + params.extent; + let coord = params.origin + id.xy; + if (any(coord >= boundary)) { + return; + } + let offset = (id.x + id.y * params.extent.x) * ${componentCount} * ${sampleCount}; + for (var sampleIndex = 0u; sampleIndex < ${sampleCount}; sampleIndex = sampleIndex + 1) { - let o = sampleIndex * ${componentCount}; - let v = textureLoad(src, coord, sampleIndex); - for (var component = 0; component < ${componentCount}; component = component + 1) { + let o = offset + sampleIndex * ${componentCount}; + let v = textureLoad(src, coord.xy, sampleIndex); + for (var component = 0u; component < ${componentCount}; component = component + 1) { dst.data[o + component] = v[component]; } } @@ -810,11 +908,16 @@ export class GPUTestBase extends Fixture { }); const storageBuffer = this.device.createBuffer({ - size: sampleCount * type.size * componentCount, + size: sampleCount * type.size * componentCount * width * height, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC }); this.trackForCleanup(storageBuffer); + const uniformBuffer = this.makeBufferWithContents( + new Uint32Array([origin.x, origin.y, width, height]), + GPUBufferUsage.UNIFORM + ); + const uniformBindGroup = this.device.createBindGroup({ layout: computePipeline.getBindGroupLayout(0), entries: [ @@ -827,6 +930,12 @@ export class GPUTestBase extends Fixture { resource: { buffer: storageBuffer } + }, + { + binding: 2, + resource: { + buffer: uniformBuffer + } }] }); @@ -835,7 +944,11 @@ export class GPUTestBase extends Fixture { const pass = encoder.beginComputePass(); pass.setPipeline(computePipeline); pass.setBindGroup(0, uniformBindGroup); - pass.dispatchWorkgroups(1); + pass.dispatchWorkgroups( + Math.floor((width + kWorkgroupSizeX - 1) / kWorkgroupSizeX), + Math.floor((height + kWorkgroupSizeY - 1) / kWorkgroupSizeY), + 1 + ); pass.end(); this.device.queue.submit([encoder.finish()]); @@ -1081,11 +1194,17 @@ export class GPUTest extends GPUTestBase { this.mismatchedProvider = await this.sharedState.acquireMismatchedProvider(); } + /** GPUAdapter that the device was created from. */ + get adapter() { + assert(this.provider !== undefined, 'internal error: DeviceProvider missing'); + return this.provider.adapter; + } + /** * GPUDevice for the test to use. */ get device() { - assert(this.provider !== undefined, 'internal error: GPUDevice missing?'); + assert(this.provider !== undefined, 'internal error: DeviceProvider missing'); return this.provider.device; } @@ -1241,13 +1360,30 @@ export class GPUTest extends GPUTestBase { + + const s_deviceToResourcesMap = new WeakMap(); /** * Gets a (cached) pipeline to render a texture to an rgba8unorm texture */ -function getPipelineToRenderTextureToRGB8UnormTexture(device) { +function getPipelineToRenderTextureToRGB8UnormTexture( +device, +texture, +isCompatibility) +{ if (!s_deviceToResourcesMap.has(device)) { + s_deviceToResourcesMap.set(device, { + pipelineByPipelineType: new Map() + }); + } + + const { pipelineByPipelineType } = s_deviceToResourcesMap.get(device); + const pipelineType = + isCompatibility && texture.depthOrArrayLayers > 1 ? '2d-array' : '2d'; + if (!pipelineByPipelineType.get(pipelineType)) { + const [textureType, layerCode] = + pipelineType === '2d' ? ['texture_2d', ''] : ['texture_2d_array', ', uni.baseArrayLayer']; const module = device.createShaderModule({ code: ` struct VSOutput { @@ -1255,6 +1391,10 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device) { @location(0) texcoord: vec2f, }; + struct Uniforms { + baseArrayLayer: u32, + }; + @vertex fn vs( @builtin(vertex_index) vertexIndex : u32 ) -> VSOutput { @@ -1275,10 +1415,11 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device) { } @group(0) @binding(0) var ourSampler: sampler; - @group(0) @binding(1) var ourTexture: texture_2d<f32>; + @group(0) @binding(1) var ourTexture: ${textureType}<f32>; + @group(0) @binding(2) var<uniform> uni: Uniforms; @fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f { - return textureSample(ourTexture, ourSampler, fsInput.texcoord); + return textureSample(ourTexture, ourSampler, fsInput.texcoord${layerCode}); } ` }); @@ -1294,10 +1435,10 @@ function getPipelineToRenderTextureToRGB8UnormTexture(device) { targets: [{ format: 'rgba8unorm' }] } }); - s_deviceToResourcesMap.set(device, { pipeline }); + pipelineByPipelineType.set(pipelineType, pipeline); } - const { pipeline } = s_deviceToResourcesMap.get(device); - return pipeline; + const pipeline = pipelineByPipelineType.get(pipelineType); + return { pipelineType, pipeline }; } @@ -1441,7 +1582,11 @@ Base) // Render every layer of both textures at mipLevel to an rgba8unorm texture // that matches the size of the mipLevel. After each render, copy the // result to a buffer and expect the results from both textures to match. - const pipeline = getPipelineToRenderTextureToRGB8UnormTexture(this.device); + const { pipelineType, pipeline } = getPipelineToRenderTextureToRGB8UnormTexture( + this.device, + actualTexture, + this.isCompatibility + ); const readbackPromisesPerTexturePerLayer = [actualTexture, expectedTexture].map( (texture, ndx) => { const attachmentSize = virtualMipSize('2d', [texture.width, texture.height, 1], mipLevel); @@ -1457,24 +1602,45 @@ Base) const numLayers = texture.depthOrArrayLayers; const readbackPromisesPerLayer = []; + + const uniformBuffer = this.device.createBuffer({ + size: 4, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }); + this.trackForCleanup(uniformBuffer); + for (let layer = 0; layer < numLayers; ++layer) { + const viewDescriptor = { + baseMipLevel: mipLevel, + mipLevelCount: 1, + ...(!this.isCompatibility && { + baseArrayLayer: layer, + arrayLayerCount: 1 + }), + dimension: pipelineType + }; + const bindGroup = this.device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [ { binding: 0, resource: sampler }, { binding: 1, - resource: texture.createView({ - baseMipLevel: mipLevel, - mipLevelCount: 1, - baseArrayLayer: layer, - arrayLayerCount: 1, - dimension: '2d' - }) - }] + resource: texture.createView(viewDescriptor) + }, + ...(pipelineType === '2d-array' ? + [ + { + binding: 2, + resource: { buffer: uniformBuffer } + }] : + + [])] }); + this.device.queue.writeBuffer(uniformBuffer, 0, new Uint32Array([layer])); + const encoder = this.device.createCommandEncoder(); const pass = encoder.beginRenderPass({ colorAttachments: [ |