diff --git a/src/webgpu/format_info.ts b/src/webgpu/format_info.ts index 620fe7a294e9..8c08b7b8e69d 100644 --- a/src/webgpu/format_info.ts +++ b/src/webgpu/format_info.ts @@ -1553,6 +1553,13 @@ const kTextureFormatInfo_TypeCheck: { readonly [F in GPUTextureFormat]: TextureFormatInfo_TypeCheck; } = kTextureFormatInfo; +// Depth texture formats including formats that also support stencil +export const kDepthTextureFormats = kDepthStencilFormats.filter(v => kTextureFormatInfo[v].depth); +// Stencil texture formats including formats that also support depth +export const kStencilTextureFormats = kDepthStencilFormats.filter( + v => kTextureFormatInfo[v].stencil +); + // Texture formats that may possibly be used as a storage texture. // Some may require certain features to be enabled. export const kPossibleStorageTextureFormats = [ @@ -1917,6 +1924,23 @@ export function isTextureFormatPossiblyUsableAsRenderAttachment(format: GPUTextu return format === 'rg11b10ufloat' || isDepthOrStencilTextureFormat(format) || !!info.colorRender; } +/** + * Returns true if a texture can possibly be used multisampled. + * The texture may require certain features to be enabled. + */ +export function isTextureFormatPossiblyMultisampled(format: GPUTextureFormat) { + const info = kTextureFormatInfo[format]; + return format === 'rg11b10ufloat' || info.multisample; +} + +/** + * Returns true if a texture can possibly be used as a writable storage texture. + * The texture may require certain features to be enabled. + */ +export function isTextureFormatPossiblyStorageWritable(format: GPUTextureFormat) { + return kTextureFormatInfo[format].color?.readWriteStorage; +} + export function is16Float(format: GPUTextureFormat) { return format === 'r16float' || format === 'rg16float' || format === 'rgba16float'; } diff --git a/src/webgpu/gpu_test.ts b/src/webgpu/gpu_test.ts index 0b96aaf7558b..3b6968ff93a0 100644 --- a/src/webgpu/gpu_test.ts +++ b/src/webgpu/gpu_test.ts @@ -631,6 +631,19 @@ export class GPUTestBase extends Fixture { } } + skipIfTextureLoadNotSupportedForTextureType(...types: (string | undefined | null)[]) { + if (this.isCompatibility) { + for (const type of types) { + switch (type) { + case 'texture_depth_2d': + case 'texture_depth_2d_array': + case 'texture_depth_multisampled_2d': + this.skip(`${type} is not supported by textureLoad in compatibility mode`); + } + } + } + } + skipIfTextureFormatNotUsableAsStorageTexture(...formats: (GPUTextureFormat | undefined)[]) { for (const format of formats) { if (format && !isTextureFormatUsableAsStorageFormat(this.device, format)) { diff --git a/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts index ad3b43b6bb1c..f70218437e90 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/textureDimensions.spec.ts @@ -9,9 +9,14 @@ If level is outside the range [0, textureNumLevels(t)) then any valid value for import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { + getBlockInfoForTextureFormat, + isDepthTextureFormat, + isStencilTextureFormat, + isTextureFormatPossiblyMultisampled, + isTextureFormatPossiblyStorageWritable, kAllTextureFormats, - kColorTextureFormats, - kTextureFormatInfo, + kDepthTextureFormats, + kPossibleStorageTextureFormats, sampleTypeForFormatAndAspect, textureDimensionAndFormatCompatible, } from '../../../../../format_info.js'; @@ -41,8 +46,7 @@ const kAllViewDimensions: readonly GPUTextureViewDimension[] = [ /** @returns the aspects to test for the given format */ function aspectsForFormat(format: GPUTextureFormat): readonly GPUTextureAspect[] { - const formatInfo = kTextureFormatInfo[format]; - if (formatInfo.depth !== undefined && formatInfo.stencil !== undefined) { + if (isDepthTextureFormat(format) && isStencilTextureFormat(format)) { return ['depth-only', 'stencil-only']; } return ['all']; @@ -50,8 +54,7 @@ function aspectsForFormat(format: GPUTextureFormat): readonly GPUTextureAspect[] /** @returns the sample counts to test for the given format */ function samplesForFormat(format: GPUTextureFormat): readonly number[] { - const info = kTextureFormatInfo[format]; - return info.multisample ? [1, kMaxSamplesForTest] : [1]; + return isTextureFormatPossiblyMultisampled(format) ? [1, kMaxSamplesForTest] : [1]; } /** @@ -157,7 +160,7 @@ function testValues(params: { const kMinLen = 1 << kMaxMipsForTest; const kNumCubeFaces = 6; - const formatInfo = kTextureFormatInfo[params.format]; + const formatInfo = getBlockInfoForTextureFormat(params.format); const bw = formatInfo.blockWidth; const bh = formatInfo.blockHeight; let mip = params.baseMipLevel; @@ -286,7 +289,6 @@ Parameters: .params(u => u .combine('format', kAllTextureFormats) - .unless(p => kTextureFormatInfo[p.format].color?.type === 'unfilterable-float') .expand('aspect', u => aspectsForFormat(u.format)) .expand('samples', u => samplesForFormat(u.format)) .beginSubcases() @@ -296,17 +298,12 @@ Parameters: .expand('baseMipLevel', baseMipLevel) .expand('textureDimensionsLevel', textureDimensionsLevel) ) - .beforeAllSubcases(t => { - const info = kTextureFormatInfo[t.params.format]; - t.skipIfTextureFormatNotSupportedDeprecated(t.params.format); + .fn(t => { + t.skipIfTextureFormatNotSupported(t.params.format); + t.skipIfTextureViewDimensionNotSupported(t.params.dimensions); if (t.params.samples > 1) { - // multisampled texture requires GPUTextureUsage.RENDER_ATTACHMENT usage - t.skipIfMultisampleNotSupportedForFormatDeprecated(t.params.format); + t.skipIfTextureFormatNotMultisampled(t.params.format); } - t.selectDeviceOrSkipTestCase(info.feature); - }) - .fn(t => { - t.skipIfTextureViewDimensionNotSupportedDeprecated(t.params.dimensions); const values = testValues(t.params); const texture = t.createTextureTracked({ size: values.size, @@ -333,13 +330,12 @@ Parameters: switch (sampleType) { case 'depth': case 'float': + case 'unfilterable-float': return `${base}_${dimensions}`; case 'uint': return `${base}_${dimensions}`; case 'sint': return `${base}_${dimensions}`; - case 'unfilterable-float': - throw new Error(`'${t.params.format}' does not support sampling`); } } @@ -377,8 +373,7 @@ Parameters: ) .params(u => u - .combine('format', kAllTextureFormats) - .filter(p => !!kTextureFormatInfo[p.format].depth) + .combine('format', kDepthTextureFormats) .expand('aspect', u => aspectsForFormat(u.format)) .unless(u => u.aspect === 'stencil-only') .expand('samples', u => samplesForFormat(u.format)) @@ -389,13 +384,9 @@ Parameters: .expand('baseMipLevel', baseMipLevel) .expand('textureDimensionsLevel', textureDimensionsLevel) ) - .beforeAllSubcases(t => { - const info = kTextureFormatInfo[t.params.format]; - t.skipIfTextureFormatNotSupportedDeprecated(t.params.format); - t.selectDeviceOrSkipTestCase(info.feature); - }) .fn(t => { - t.skipIfTextureViewDimensionNotSupportedDeprecated(t.params.dimensions); + t.skipIfTextureFormatNotSupported(t.params.format); + t.skipIfTextureViewDimensionNotSupported(t.params.dimensions); const values = testValues(t.params); const texture = t.createTextureTracked({ size: values.size, @@ -465,8 +456,7 @@ Parameters: ) .params(u => u - .combine('format', kColorTextureFormats) - .filter(p => kTextureFormatInfo[p.format].color?.storage === true) + .combine('format', kPossibleStorageTextureFormats) .expand('aspect', u => aspectsForFormat(u.format)) .beginSubcases() .combine('stage', kShaderStages) @@ -474,22 +464,15 @@ Parameters: // vertex stage can not use writable storage. .unless(t => t.stage === 'vertex' && t.access !== 'read') // Only some formats support write - .unless( - t => - kTextureFormatInfo[t.format].color.readWriteStorage === false && t.access === 'read_write' - ) + .unless(t => !isTextureFormatPossiblyStorageWritable(t.format) && t.access === 'read_write') .expand('dimensions', u => viewDimensions(u).filter(dimensionsValidForStorage)) .expand('textureMipCount', textureMipCount) .expand('baseMipLevel', baseMipLevel) ) - .beforeAllSubcases(t => { - const info = kTextureFormatInfo[t.params.format]; - t.skipIfTextureFormatNotSupportedDeprecated(t.params.format); - t.skipIfTextureFormatNotUsableAsStorageTextureDeprecated(t.params.format); - t.selectDeviceOrSkipTestCase(info.feature); - }) .fn(t => { t.skipIfNoStorageTexturesInStage(t.params.stage); + t.skipIfTextureFormatNotSupported(t.params.format); + t.skipIfTextureFormatNotUsableAsStorageTexture(t.params.format); const values = testValues(t.params); const texture = t.createTextureTracked({ diff --git a/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts index 48321d1f80dd..08c5217841ad 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/textureLoad.spec.ts @@ -21,16 +21,15 @@ import { makeTestGroup } from '../../../../../../common/framework/test_group.js' import { isCompressedFloatTextureFormat, isDepthTextureFormat, - isMultisampledTextureFormatDeprecated, - isStencilTextureFormat, - kDepthStencilFormats, kAllTextureFormats, - kTextureFormatInfo, textureDimensionAndFormatCompatible, + isCompressedTextureFormat, + kPossibleMultisampledTextureFormats, + kDepthTextureFormats, + kPossibleStorageTextureFormats, } from '../../../../../format_info.js'; import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../../../../gpu_test.js'; import { maxMipLevelCount, virtualMipSize } from '../../../../../util/texture/base.js'; -import { TexelFormats } from '../../../../types.js'; import { TextureCall, @@ -101,19 +100,15 @@ Parameters: .combine('format', kAllTextureFormats) .filter(t => textureDimensionAndFormatCompatible('1d', t.format)) // 1d textures can't have a height !== 1 - .filter(t => kTextureFormatInfo[t.format].blockHeight === 1) + .filter(t => !isCompressedTextureFormat(t.format)) .beginSubcases() .combine('samplePoints', kSamplePointMethods) .combine('C', ['i32', 'u32'] as const) .combine('L', ['i32', 'u32'] as const) ) - .beforeAllSubcases(t => { - const { format } = t.params; - t.skipIfTextureFormatNotSupportedDeprecated(format); - t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format); - }) .fn(async t => { const { format, stage, C, L, samplePoints } = t.params; + t.skipIfTextureFormatNotSupported(format); // We want at least 4 blocks or something wide enough for 3 mip levels. const [width] = chooseTextureSize({ minSize: 8, minBlocks: 4, format }); @@ -192,13 +187,9 @@ Parameters: .combine('C', ['i32', 'u32'] as const) .combine('L', ['i32', 'u32'] as const) ) - .beforeAllSubcases(t => { - const { format } = t.params; - t.skipIfTextureFormatNotSupportedDeprecated(format); - t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format); - }) .fn(async t => { const { format, stage, samplePoints, C, L } = t.params; + t.skipIfTextureFormatNotSupported(format); // We want at least 4 blocks or something wide enough for 3 mip levels. const size = chooseTextureSize({ minSize: 8, minBlocks: 4, format }); @@ -275,13 +266,9 @@ Parameters: .combine('C', ['i32', 'u32'] as const) .combine('L', ['i32', 'u32'] as const) ) - .beforeAllSubcases(t => { - const { format } = t.params; - t.skipIfTextureFormatNotSupportedDeprecated(format); - t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format); - }) .fn(async t => { const { format, stage, samplePoints, C, L } = t.params; + t.skipIfTextureFormatNotSupported(format); // We want at least 4 blocks or something wide enough for 3 mip levels. const size = chooseTextureSize({ minSize: 8, minBlocks: 4, format, viewDimension: '3d' }); @@ -358,9 +345,7 @@ Parameters: 'texture_multisampled_2d', 'texture_depth_multisampled_2d', ] as const) - .combine('format', kAllTextureFormats) - .filter(t => isMultisampledTextureFormatDeprecated(t.format, false)) - .filter(t => !isStencilTextureFormat(t.format)) + .combine('format', kPossibleMultisampledTextureFormats) // Filter out texture_depth_multisampled_2d with non-depth formats .filter( t => @@ -371,14 +356,11 @@ Parameters: .combine('C', ['i32', 'u32'] as const) .combine('S', ['i32', 'u32'] as const) ) - .beforeAllSubcases(t => { - const { format, texture_type } = t.params; - t.skipIfTextureFormatNotSupportedDeprecated(format); - t.skipIfTextureLoadNotSupportedForTextureTypeDeprecated(texture_type); - t.skipIfMultisampleNotSupportedForFormatDeprecated(format); - }) .fn(async t => { const { texture_type, format, stage, samplePoints, C, S } = t.params; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureLoadNotSupportedForTextureType(texture_type); + t.skipIfTextureFormatNotMultisampled(format); const sampleCount = 4; const descriptor: GPUTextureDescriptor = { @@ -446,20 +428,16 @@ Parameters: .params(u => u .combine('stage', kShortShaderStages) - .combine('format', kDepthStencilFormats) - // filter out stencil only formats - .filter(t => isDepthTextureFormat(t.format)) + .combine('format', kDepthTextureFormats) .beginSubcases() .combine('samplePoints', kSamplePointMethods) .combine('C', ['i32', 'u32'] as const) .combine('L', ['i32', 'u32'] as const) ) - .beforeAllSubcases(t => { - t.skipIfTextureLoadNotSupportedForTextureTypeDeprecated('texture_depth_2d'); - t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format); - }) .fn(async t => { const { format, stage, samplePoints, C, L } = t.params; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureLoadNotSupportedForTextureType('texture_depth_2d'); // We want at least 4 blocks or something wide enough for 3 mip levels. const size = chooseTextureSize({ minSize: 8, minBlocks: 4, format }); @@ -623,14 +601,10 @@ Parameters: ] as const) .combine('depthOrArrayLayers', [1, 8] as const) ) - .beforeAllSubcases(t => { - const { format, texture_type } = t.params; - t.skipIfTextureFormatNotSupportedDeprecated(format); - t.skipIfTextureLoadNotSupportedForTextureTypeDeprecated(texture_type); - t.selectDeviceForTextureFormatOrSkipTestCase(format); - }) .fn(async t => { const { texture_type, format, stage, samplePoints, C, A, L, depthOrArrayLayers } = t.params; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureLoadNotSupportedForTextureType(texture_type); // We want at least 4 blocks or something wide enough for 3 mip levels. const [width, height] = chooseTextureSize({ minSize: 8, minBlocks: 4, format }); @@ -702,22 +676,18 @@ Parameters: .params(u => u .combine('stage', kShortShaderStages) - .combineWithParams([...TexelFormats, { format: 'bgra8unorm' }] as const) + .combine('format', kPossibleStorageTextureFormats) .beginSubcases() .combine('samplePoints', kSamplePointMethods) .combine('C', ['i32', 'u32'] as const) ) - .beforeAllSubcases(t => { - t.skipIf(!t.hasLanguageFeature('readonly_and_readwrite_storage_textures')); - if (t.params.format === 'bgra8unorm') { - t.selectDeviceOrSkipTestCase('bgra8unorm-storage'); - } else { - t.skipIfTextureFormatNotUsableAsStorageTextureDeprecated(t.params.format as GPUTextureFormat); - } - }) + .beforeAllSubcases(t => + t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures') + ) .fn(async t => { const { format, stage, samplePoints, C } = t.params; + t.skipIfTextureFormatNotUsableAsStorageTexture(format); skipIfStorageTexturesNotSupportedInStage(t, stage); // We want at least 3 blocks or something wide enough for 3 mip levels. @@ -783,22 +753,19 @@ Parameters: .params(u => u .combine('stage', kShortShaderStages) - .combineWithParams([...TexelFormats, { format: 'bgra8unorm' }] as const) + .combine('format', kPossibleStorageTextureFormats) .beginSubcases() .combine('samplePoints', kSamplePointMethods) .combine('C', ['i32', 'u32'] as const) ) - .beforeAllSubcases(t => { - t.skipIf(!t.hasLanguageFeature('readonly_and_readwrite_storage_textures')); - if (t.params.format === 'bgra8unorm') { - t.selectDeviceOrSkipTestCase('bgra8unorm-storage'); - } else { - t.skipIfTextureFormatNotUsableAsStorageTextureDeprecated(t.params.format as GPUTextureFormat); - } - }) + .beforeAllSubcases(t => + t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures') + ) .fn(async t => { const { format, stage, samplePoints, C } = t.params; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureFormatNotUsableAsStorageTexture(format); skipIfStorageTexturesNotSupportedInStage(t, stage); // We want at least 3 blocks or something wide enough for 3 mip levels. @@ -864,24 +831,21 @@ Parameters: .params(u => u .combine('stage', kShortShaderStages) - .combineWithParams([...TexelFormats, { format: 'bgra8unorm' }] as const) + .combine('format', kPossibleStorageTextureFormats) .beginSubcases() .combine('samplePoints', kSamplePointMethods) .combine('C', ['i32', 'u32'] as const) .combine('A', ['i32', 'u32'] as const) .combine('depthOrArrayLayers', [1, 8] as const) ) - .beforeAllSubcases(t => { - t.skipIf(!t.hasLanguageFeature('readonly_and_readwrite_storage_textures')); - if (t.params.format === 'bgra8unorm') { - t.selectDeviceOrSkipTestCase('bgra8unorm-storage'); - } else { - t.skipIfTextureFormatNotUsableAsStorageTextureDeprecated(t.params.format as GPUTextureFormat); - } - }) + .beforeAllSubcases(t => + t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures') + ) .fn(async t => { const { format, stage, samplePoints, C, A, depthOrArrayLayers } = t.params; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureFormatNotUsableAsStorageTexture(format); skipIfStorageTexturesNotSupportedInStage(t, stage); // We want at least 3 blocks or something wide enough for 3 mip levels. @@ -952,22 +916,19 @@ Parameters: .params(u => u .combine('stage', kShortShaderStages) - .combineWithParams([...TexelFormats, { format: 'bgra8unorm' }] as const) + .combine('format', kPossibleStorageTextureFormats) .beginSubcases() .combine('samplePoints', kSamplePointMethods) .combine('C', ['i32', 'u32'] as const) ) .beforeAllSubcases(t => { - t.skipIf(!t.hasLanguageFeature('readonly_and_readwrite_storage_textures')); - if (t.params.format === 'bgra8unorm') { - t.selectDeviceOrSkipTestCase('bgra8unorm-storage'); - } else { - t.skipIfTextureFormatNotUsableAsStorageTextureDeprecated(t.params.format as GPUTextureFormat); - } + t.skipIfLanguageFeatureNotSupported('readonly_and_readwrite_storage_textures'); }) .fn(async t => { const { format, stage, samplePoints, C } = t.params; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureFormatNotUsableAsStorageTexture(format); skipIfStorageTexturesNotSupportedInStage(t, stage); // We want at least 3 blocks or something wide enough for 3 mip levels. diff --git a/src/webgpu/util/texture.ts b/src/webgpu/util/texture.ts index 373b495ddd59..995d52266cd3 100644 --- a/src/webgpu/util/texture.ts +++ b/src/webgpu/util/texture.ts @@ -114,6 +114,11 @@ const kLoadValueFromStorageInfo: Partial<{ texelType: 'vec4i', unpackWGSL: 'return unpack4xI8(getSrc(byteOffset / 4))', }, + rg11b10ufloat: { + storageType: 'u32', + texelType: 'vec4f', + unpackWGSL: `return unpackRG11B10UFloat(getSrc(byteOffset / 4))`, + }, r16float: { storageType: 'u32', texelType: 'vec4f', @@ -308,6 +313,53 @@ function getCopyBufferToTextureViaRenderCode( return textureLoad(src, vec2u(x, y), 0).r; } + const kFloat32FormatMantissaBits = 23; + const kFloat32FormatBias = 127; + fn floatBitsToNumber( + rawBits: u32, + bitOffset: u32, + exponentBits: u32, + mantissaBits: u32, + bias: u32, + signed: bool) -> f32 { + let nonSignBits = exponentBits + mantissaBits; + let allBits = nonSignBits + select(0u, 1u, signed); + let allMask = (1u << allBits) - 1u; + let bits = (rawBits >> bitOffset) & allMask; + let nonSignBitsMask = (1u << nonSignBits) - 1u; + let exponentAndMantissaBits = bits & nonSignBitsMask; + let exponentMask = ((1u << exponentBits) - 1u) << mantissaBits; + let infinityOrNaN = (bits & exponentMask) == exponentMask; + if (infinityOrNaN) { + let mantissaMask = (1u << mantissaBits) - 1; + let signBit = 1u << nonSignBits; + let isNegative = (bits & signBit) != 0; + if ((bits & mantissaMask) != 0u) { + return 0.0; // NaN (does not exist in WGSL) + } + if (isNegative) { + return f32(-2e38); // NEGATIVE_INFINITY (does not exist in WGSL) + } else { + return f32(2e38); // POSITIVE_INFINITY (does not exist in WGSL) + } + } + var f32BitsWithWrongBias = + exponentAndMantissaBits << (kFloat32FormatMantissaBits - mantissaBits); + // add in the sign + f32BitsWithWrongBias |= (bits << (31u - nonSignBits)) & 0x80000000u; + let numberWithWrongBias = bitcast(f32BitsWithWrongBias); + return numberWithWrongBias * pow(2.0f, f32(kFloat32FormatBias - bias)); + } + + fn unpackRG11B10UFloat(v: u32) -> vec4f { + return vec4f( + floatBitsToNumber(v, 0, 5, 6, 15, false), + floatBitsToNumber(v, 11, 5, 6, 15, false), + floatBitsToNumber(v, 22, 5, 5, 15, false), + 1 + ); + } + fn unpack(byteOffset: u32) -> ${texelType} { ${unpackWGSL}; }