diff --git a/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts b/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts index 6728b0a5316..65f4e0b8d95 100644 --- a/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts +++ b/src/webgpu/api/validation/render_pass/render_pass_descriptor.spec.ts @@ -12,16 +12,19 @@ import { GPUConst } from '../../../constants.js'; import { computeBytesPerSampleFromFormats, kDepthStencilFormats, - kRenderableColorTextureFormats, - kTextureFormatInfo, + kPossiblyRenderableColorTextureFormats, + isTextureFormatColorRenderable, + isDepthTextureFormat, + isStencilTextureFormat, + isTextureFormatResolvable, } from '../../../format_info.js'; -import { ValidationTest } from '../validation_test.js'; +import { AllFeaturesMaxLimitsValidationTest } from '../validation_test.js'; // MAINTENANCE_TODO: This should be changed to kMaxColorAttachmentsToTest // when this is made a MaxLimitTest (see above). const kMaxColorAttachments = getDefaultLimits('core').maxColorAttachments.default; -class F extends ValidationTest { +class F extends AllFeaturesMaxLimitsValidationTest { createTestTexture( options: { format?: GPUTextureFormat; @@ -202,20 +205,18 @@ g.test('color_attachments,limits,maxColorAttachmentBytesPerSample,aligned') ) .params(u => u - .combine('format', kRenderableColorTextureFormats) + .combine('format', kPossiblyRenderableColorTextureFormats) .beginSubcases() .combine( 'attachmentCount', range(kMaxColorAttachments, i => i + 1) ) ) - .beforeAllSubcases(t => { - t.skipIfTextureFormatNotSupportedDeprecated(t.params.format); - }) .fn(t => { const { format, attachmentCount } = t.params; - const info = kTextureFormatInfo[format]; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureFormatNotUsableAsRenderAttachment(format); t.skipIf( attachmentCount > t.device.limits.maxColorAttachments, `attachmentCount: ${attachmentCount} > maxColorAttachments: ${t.device.limits.maxColorAttachments}` @@ -227,7 +228,7 @@ g.test('color_attachments,limits,maxColorAttachmentBytesPerSample,aligned') colorAttachments.push(t.getColorAttachment(colorTexture)); } const shouldError = - info.colorRender === undefined || + !isTextureFormatColorRenderable(t.device, format) || computeBytesPerSampleFromFormats(range(attachmentCount, () => format)) > t.device.limits.maxColorAttachmentBytesPerSample; @@ -1049,10 +1050,6 @@ g.test('depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadO .combine('stencilLoadOp', [undefined, 'clear', 'load'] as GPULoadOp[]) .combine('stencilStoreOp', [undefined, 'discard', 'store'] as GPUStoreOp[]) ) - .beforeAllSubcases(t => { - const info = kTextureFormatInfo[t.params.format as GPUTextureFormat]; - t.selectDeviceOrSkipTestCase(info.feature); - }) .fn(t => { const { format, @@ -1064,6 +1061,8 @@ g.test('depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadO stencilStoreOp, } = t.params; + t.skipIfTextureFormatNotSupported(format); + const depthAttachment = t.createTextureTracked({ format, size: { width: 1, height: 1, depthOrArrayLayers: 1 }, @@ -1092,11 +1091,10 @@ g.test('depth_stencil_attachment,loadOp_storeOp_match_depthReadOnly_stencilReadO const pass = encoder.beginRenderPass(renderPassDescriptor); pass.end(); - const info = kTextureFormatInfo[format]; const hasDepthSettings = !!depthLoadOp && !!depthStoreOp && !depthReadOnly; const hasStencilSettings = !!stencilLoadOp && !!stencilStoreOp && !stencilReadOnly; - const hasDepth = info.depth; - const hasStencil = info.stencil; + const hasDepth = isDepthTextureFormat(format); + const hasStencil = isStencilTextureFormat(format); const goodAspectSettingsPresent = (hasDepthSettings ? hasDepth : true) && (hasStencilSettings ? hasStencil : true); @@ -1162,19 +1160,11 @@ g.test('resolveTarget,format_supports_resolve') if and only if they support 'resolve'. ` ) - .params(u => - u - .combine('format', kRenderableColorTextureFormats) - .filter(t => kTextureFormatInfo[t.format].multisample) - ) - .beforeAllSubcases(t => { - const { format } = t.params; - t.skipIfTextureFormatNotSupportedDeprecated(format); - t.skipIfMultisampleNotSupportedForFormatDeprecated(format); - }) + .params(u => u.combine('format', kPossiblyRenderableColorTextureFormats)) .fn(t => { const { format } = t.params; - const info = kTextureFormatInfo[format]; + t.skipIfTextureFormatNotSupported(format); + t.skipIfMultisampleNotSupportedForFormat(format); const multisampledColorTexture = t.createTestTexture({ format, sampleCount: 4 }); const resolveTarget = t.createTestTexture({ format }); @@ -1182,7 +1172,7 @@ g.test('resolveTarget,format_supports_resolve') const colorAttachment = t.getColorAttachment(multisampledColorTexture); colorAttachment.resolveTarget = resolveTarget.createView(); - t.tryRenderPass(!!info.colorRender?.resolve, { + t.tryRenderPass(isTextureFormatResolvable(t.device, format), { colorAttachments: [colorAttachment], }); }); @@ -1198,10 +1188,8 @@ g.test('timestampWrites,query_set_type') u // .combine('queryType', kQueryTypes) ) - .beforeAllSubcases(t => { - t.selectDeviceOrSkipTestCase(['timestamp-query']); - }) .fn(t => { + t.skipIfDeviceDoesNotSupportQueryType('timestamp'); const { queryType } = t.params; const timestampWrites = { @@ -1231,10 +1219,8 @@ g.test('timestampWrite,query_index') .combine('beginningOfPassWriteIndex', [undefined, 0, 1, 2, 3] as const) .combine('endOfPassWriteIndex', [undefined, 0, 1, 2, 3] as const) ) - .beforeAllSubcases(t => { - t.selectDeviceOrSkipTestCase(['timestamp-query']); - }) .fn(t => { + t.skipIfDeviceDoesNotSupportQueryType('timestamp'); const { beginningOfPassWriteIndex, endOfPassWriteIndex } = t.params; const querySetCount = 2; @@ -1262,13 +1248,9 @@ g.test('timestampWrite,query_index') g.test('occlusionQuerySet,query_set_type') .desc(`Test that occlusionQuerySet must have type 'occlusion'.`) .params(u => u.combine('queryType', kQueryTypes)) - .beforeAllSubcases(t => { - if (t.params.queryType === 'timestamp') { - t.selectDeviceOrSkipTestCase(['timestamp-query']); - } - }) .fn(t => { const { queryType } = t.params; + t.skipIfDeviceDoesNotSupportQueryType(queryType); const querySet = t.createQuerySetTracked({ type: queryType, diff --git a/src/webgpu/format_info.ts b/src/webgpu/format_info.ts index a7205cce725..61b0de7b280 100644 --- a/src/webgpu/format_info.ts +++ b/src/webgpu/format_info.ts @@ -1438,10 +1438,19 @@ const kASTCTextureFormatInfo = formatTableWithDefaults({ /* prettier-ignore */ export const kAllTextureFormats: readonly GPUTextureFormat[] = keysOf( kAllTextureFormatInfo); // CompressedTextureFormat are unrenderable so filter from RegularTextureFormats for color targets is enough +// @deprecated export const kRenderableColorTextureFormats = kRegularTextureFormats.filter( v => kColorTextureFormatInfo[v].colorRender ); +// Color formats that are possibly renderable. Some may require features to be enabled. +// MAINTENANCE_TODO: remove 'rg11b10ufloat` once colorRender is added to its info. +// See: computeBytesPerSampleFromFormats +export const kPossiblyRenderableColorTextureFormats = [ + ...kRegularTextureFormats.filter(v => kColorTextureFormatInfo[v].colorRender), + 'rg11b10ufloat', +] as const; + /** Per-GPUTextureFormat-per-aspect info. */ interface TextureFormatAspectInfo { /** Whether the aspect can be used as `COPY_SRC`. */ @@ -2042,7 +2051,14 @@ export const kFeaturesForFormats = getFeaturesForFormats(kAllTextureFormats); export function computeBytesPerSampleFromFormats(formats: readonly GPUTextureFormat[]) { let bytesPerSample = 0; for (const format of formats) { - const info = kTextureFormatInfo[format]; + // MAINTENANCE_TODO: Add colorRender to rg11b10ufloat format in kTextureFormatInfo + // The issue is if we add it now lots of tests will break as they'll think they can + // render to the format but are not enabling 'rg11b10ufloat-renderable'. Once we + // get the CTS refactored (see issue 4181), then fix this. + const info = + format === 'rg11b10ufloat' + ? { colorRender: { alignment: 4, byteCost: 8 } } + : kTextureFormatInfo[format]; const alignedBytesPerSample = align(bytesPerSample, info.colorRender!.alignment); bytesPerSample = alignedBytesPerSample + info.colorRender!.byteCost; } diff --git a/src/webgpu/gpu_test.ts b/src/webgpu/gpu_test.ts index 3b42a13a33e..1a9c604bcd4 100644 --- a/src/webgpu/gpu_test.ts +++ b/src/webgpu/gpu_test.ts @@ -36,6 +36,7 @@ import { isMultisampledTextureFormatDeprecated, isTextureFormatUsableAsStorageFormat, isTextureFormatUsableAsRenderAttachment, + isTextureFormatMultisampled, } from './format_info.js'; import { checkElementsEqual, checkElementsBetween } from './util/check_contents.js'; import { CommandBufferMaker, EncoderType } from './util/command_buffer_maker.js'; @@ -524,6 +525,26 @@ export class GPUTestBase extends Fixture { } } + /** + * Skips test if device does not have feature. + * Note: Try to use one of the more specific skipIf tests if possible. + */ + skipIfDeviceDoesNotHaveFeature(feature: GPUFeatureName) { + this.skipIf(!this.device.features.has(feature), `device does not have feature: '${feature}'`); + } + + /** + * Skips test if device des not support query type. + */ + skipIfDeviceDoesNotSupportQueryType(...types: GPUQueryType[]) { + for (const type of types) { + const feature = kQueryTypeInfo[type].feature; + if (feature) { + this.skipIfDeviceDoesNotHaveFeature(feature); + } + } + } + /** * Skips test if any format is not supported. */ @@ -545,6 +566,15 @@ export class GPUTestBase extends Fixture { } } + skipIfMultisampleNotSupportedForFormat(...formats: (GPUTextureFormat | undefined)[]) { + for (const format of formats) { + if (format === undefined) continue; + if (!isTextureFormatMultisampled(this.device, format)) { + this.skip(`texture format '${format}' is not supported to be multisampled`); + } + } + } + /** @deprecated */ skipIfTextureViewDimensionNotSupportedDeprecated( ...dimensions: (GPUTextureViewDimension | undefined)[]