From 7e7cbe8d36373ba5627ea9e6ac62b4436e8bbd69 Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Mon, 24 Feb 2025 14:40:43 -0800 Subject: [PATCH] Refactor api/operation/render_pipeline for texture formats Issue #4181 Issue #4178 --- .../render_pipeline/culling_tests.spec.ts | 8 +-- .../render_pipeline/overrides.spec.ts | 4 +- .../pipeline_output_targets.spec.ts | 55 ++++++++----------- .../primitive_topology.spec.ts | 4 +- .../render_pipeline/sample_mask.spec.ts | 4 +- .../vertex_only_render_pipeline.spec.ts | 4 +- src/webgpu/format_info.ts | 17 ++++-- src/webgpu/gpu_test.ts | 13 +++++ 8 files changed, 62 insertions(+), 47 deletions(-) diff --git a/src/webgpu/api/operation/render_pipeline/culling_tests.spec.ts b/src/webgpu/api/operation/render_pipeline/culling_tests.spec.ts index 0dd6794942dd..2551f8f768ea 100644 --- a/src/webgpu/api/operation/render_pipeline/culling_tests.spec.ts +++ b/src/webgpu/api/operation/render_pipeline/culling_tests.spec.ts @@ -5,8 +5,8 @@ Test all culling combinations of GPUFrontFace and GPUCullMode show the correct o `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; -import { kTextureFormatInfo, SizedTextureFormat } from '../../../format_info.js'; -import { GPUTest, TextureTestMixin } from '../../../gpu_test.js'; +import { isStencilTextureFormat, SizedTextureFormat } from '../../../format_info.js'; +import { AllFeaturesMaxLimitsGPUTest, TextureTestMixin } from '../../../gpu_test.js'; function faceIsCulled(face: 'cw' | 'ccw', frontFace: GPUFrontFace, cullMode: GPUCullMode): boolean { return cullMode !== 'none' && (frontFace === face) === (cullMode === 'front'); @@ -24,7 +24,7 @@ function faceColor(face: 'cw' | 'ccw', frontFace: GPUFrontFace, cullMode: GPUCul } } -class CullingTest extends TextureTestMixin(GPUTest) { +class CullingTest extends TextureTestMixin(AllFeaturesMaxLimitsGPUTest) { checkCornerPixels( texture: GPUTexture, expectedTopLeftColor: Uint8Array, @@ -146,7 +146,7 @@ g.test('culling') usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, }); - const haveStencil = depthStencilFormat && kTextureFormatInfo[depthStencilFormat].stencil; + const haveStencil = depthStencilFormat && isStencilTextureFormat(depthStencilFormat); let depthTexture: GPUTexture | undefined = undefined; let depthStencilAttachment: GPURenderPassDepthStencilAttachment | undefined = undefined; let depthStencil: GPUDepthStencilState | undefined = undefined; diff --git a/src/webgpu/api/operation/render_pipeline/overrides.spec.ts b/src/webgpu/api/operation/render_pipeline/overrides.spec.ts index 7d00015d5e4f..113922f381c6 100644 --- a/src/webgpu/api/operation/render_pipeline/overrides.spec.ts +++ b/src/webgpu/api/operation/render_pipeline/overrides.spec.ts @@ -3,10 +3,10 @@ Testing render pipeline using overridable constants in vertex stage and fragment `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; -import { GPUTest } from '../../../gpu_test.js'; +import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js'; import { PerTexelComponent } from '../../../util/texture/texel_data.js'; -class F extends GPUTest { +class F extends AllFeaturesMaxLimitsGPUTest { async ExpectShaderOutputWithConstants( isAsync: boolean, format: GPUTextureFormat, diff --git a/src/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.ts b/src/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.ts index 6fd6719158e5..1c4acf50157a 100644 --- a/src/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.ts +++ b/src/webgpu/api/operation/render_pipeline/pipeline_output_targets.spec.ts @@ -6,10 +6,12 @@ import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { range } from '../../../../common/util/util.js'; import { computeBytesPerSampleFromFormats, - kRenderableColorTextureFormats, - kTextureFormatInfo, + getColorRenderByteCost, + getTextureFormatType, + isSintOrUintFormat, + kPossibleColorRenderableTextureFormats, } from '../../../format_info.js'; -import { GPUTest, TextureTestMixin } from '../../../gpu_test.js'; +import { AllFeaturesMaxLimitsGPUTest, TextureTestMixin } from '../../../gpu_test.js'; import { getFragmentShaderCodeWithOutput, getPlainTypeInfo } from '../../../util/shader.js'; import { kTexelRepresentationInfo } from '../../../util/texture/texel_data.js'; @@ -25,7 +27,7 @@ const kVertexShader = ` } `; -export const g = makeTestGroup(TextureTestMixin(GPUTest)); +export const g = makeTestGroup(TextureTestMixin(AllFeaturesMaxLimitsGPUTest)); // Values to write into each attachment // We make values different for each attachment index and each channel @@ -50,33 +52,30 @@ g.test('color,attachments') .desc(`Test that pipeline with sparse color attachments write values correctly.`) .params(u => u - .combine('format', kRenderableColorTextureFormats) + .combine('format', kPossibleColorRenderableTextureFormats) .beginSubcases() .combine('attachmentCount', [2, 3, 4]) .expand('emptyAttachmentId', p => range(p.attachmentCount, i => i)) ) - .beforeAllSubcases(t => { - const info = kTextureFormatInfo[t.params.format]; - t.skipIfTextureFormatNotSupportedDeprecated(t.params.format); - t.selectDeviceOrSkipTestCase(info.feature); - }) .fn(t => { const { format, attachmentCount, emptyAttachmentId } = t.params; + + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureFormatNotUsableAsRenderAttachment(format); + const componentCount = kTexelRepresentationInfo[format].componentOrder.length; - const info = kTextureFormatInfo[format]; // We only need to test formats that have a valid color attachment bytes per sample. - const pixelByteCost = kTextureFormatInfo[format].colorRender?.byteCost; + const pixelByteCost = getColorRenderByteCost(format); t.skipIf( pixelByteCost === undefined || computeBytesPerSampleFromFormats(range(attachmentCount, () => format)) > t.device.limits.maxColorAttachmentBytesPerSample ); - const writeValues = - info.color.type === 'sint' || info.color.type === 'uint' - ? attachmentsIntWriteValues - : attachmentsFloatWriteValues; + const writeValues = isSintOrUintFormat(format) + ? attachmentsIntWriteValues + : attachmentsFloatWriteValues; const renderTargets = range(attachmentCount, () => t.createTextureTracked({ @@ -106,7 +105,7 @@ g.test('color,attachments') writeValues[i].B, writeValues[i].A, ], - plainType: getPlainTypeInfo(info.color.type), + plainType: getPlainTypeInfo(getTextureFormatType(format)!), componentCount, } ) @@ -152,19 +151,15 @@ g.test('color,component_count') ) .params(u => u - .combine('format', kRenderableColorTextureFormats) + .combine('format', kPossibleColorRenderableTextureFormats) .beginSubcases() .combine('componentCount', [1, 2, 3, 4]) .filter(x => x.componentCount >= kTexelRepresentationInfo[x.format].componentOrder.length) ) - .beforeAllSubcases(t => { - const info = kTextureFormatInfo[t.params.format]; - t.skipIfTextureFormatNotSupportedDeprecated(t.params.format); - t.selectDeviceOrSkipTestCase(info.feature); - }) .fn(t => { const { format, componentCount } = t.params; - const info = kTextureFormatInfo[format]; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureFormatNotUsableAsRenderAttachment(format); // expected RGBA values // extra channels are discarded @@ -189,7 +184,7 @@ g.test('color,component_count') code: getFragmentShaderCodeWithOutput([ { values, - plainType: getPlainTypeInfo(info.color.type), + plainType: getPlainTypeInfo(getTextureFormatType(format)!), componentCount, }, ]), @@ -364,10 +359,6 @@ The attachment has a load value of [1, 0, 0, 1] ] as const) .filter(x => x.output.length >= kTexelRepresentationInfo[x.format].componentOrder.length) ) - .beforeAllSubcases(t => { - const info = kTextureFormatInfo[t.params.format]; - t.selectDeviceOrSkipTestCase(info.feature); - }) .fn(t => { const { format, @@ -378,8 +369,10 @@ The attachment has a load value of [1, 0, 0, 1] alphaSrcFactor, alphaDstFactor, } = t.params; + t.skipIfTextureFormatNotSupported(format); + t.skipIfTextureFormatNotBlendable(format); + const componentCount = output.length; - const info = kTextureFormatInfo[format]; const renderTarget = t.createTextureTracked({ format, @@ -400,7 +393,7 @@ The attachment has a load value of [1, 0, 0, 1] code: getFragmentShaderCodeWithOutput([ { values: output, - plainType: getPlainTypeInfo(info.color.type), + plainType: getPlainTypeInfo(getTextureFormatType(format)!), componentCount, }, ]), diff --git a/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts b/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts index 1ce0d9847e3a..4afe254840f4 100644 --- a/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts +++ b/src/webgpu/api/operation/render_pipeline/primitive_topology.spec.ts @@ -56,7 +56,7 @@ Test locations are framebuffer coordinates: `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; -import { GPUTest, TextureTestMixin } from '../../../gpu_test.js'; +import { AllFeaturesMaxLimitsGPUTest, TextureTestMixin } from '../../../gpu_test.js'; import { PerPixelComparison } from '../../../util/texture/texture_ok.js'; const kRTSize: number = 56; @@ -279,7 +279,7 @@ function generateVertexBuffer(vertexLocations: Point2D[]): Float32Array { } const kDefaultDrawCount = 6; -class PrimitiveTopologyTest extends TextureTestMixin(GPUTest) { +class PrimitiveTopologyTest extends TextureTestMixin(AllFeaturesMaxLimitsGPUTest) { makeAttachmentTexture(): GPUTexture { return this.createTextureTracked({ format: kColorFormat, diff --git a/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts b/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts index 45c40ff77f4e..46f170fa4fc4 100644 --- a/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts +++ b/src/webgpu/api/operation/render_pipeline/sample_mask.spec.ts @@ -19,7 +19,7 @@ Details could be found at: https://github.com/gpuweb/cts/issues/2201 import { makeTestGroup } from '../../../../common/framework/test_group.js'; import { assert, range } from '../../../../common/util/util.js'; -import { GPUTest, TextureTestMixin } from '../../../gpu_test.js'; +import { AllFeaturesMaxLimitsGPUTest, TextureTestMixin } from '../../../gpu_test.js'; import { checkElementsPassPredicate, checkElementsEqual } from '../../../util/check_contents.js'; import { Type } from '../../../util/conversion.js'; import { TexelView } from '../../../util/texture/texel_view.js'; @@ -263,7 +263,7 @@ struct FragmentOutput2 { } `; -class F extends TextureTestMixin(GPUTest) { +class F extends TextureTestMixin(AllFeaturesMaxLimitsGPUTest) { private sampleTexture: GPUTexture | undefined; private sampler: GPUSampler | undefined; diff --git a/src/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline.spec.ts b/src/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline.spec.ts index ef2d108f1e1d..98df372e32b5 100644 --- a/src/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline.spec.ts +++ b/src/webgpu/api/operation/render_pipeline/vertex_only_render_pipeline.spec.ts @@ -3,9 +3,9 @@ Test vertex-only render pipeline. `; import { makeTestGroup } from '../../../../common/framework/test_group.js'; -import { GPUTest } from '../../../gpu_test.js'; +import { AllFeaturesMaxLimitsGPUTest } from '../../../gpu_test.js'; -class F extends GPUTest {} +class F extends AllFeaturesMaxLimitsGPUTest {} export const g = makeTestGroup(F); diff --git a/src/webgpu/format_info.ts b/src/webgpu/format_info.ts index acb06a992fe5..465b925c4ba3 100644 --- a/src/webgpu/format_info.ts +++ b/src/webgpu/format_info.ts @@ -1565,28 +1565,29 @@ export const kStencilTextureFormats = kDepthStencilFormats.filter( // Texture formats that may possibly be used as a storage texture. // Some may require certain features to be enabled. export const kPossibleStorageTextureFormats = [ - ...kAllTextureFormats.filter(f => kTextureFormatInfo[f].color?.storage), + ...kRegularTextureFormats.filter(f => kTextureFormatInfo[f].color?.storage), 'bgra8unorm', ] as const; // Texture formats that may possibly be multisampled. // Some may require certain features to be enabled. export const kPossibleMultisampledTextureFormats = [ - ...kAllTextureFormats.filter(f => kTextureFormatInfo[f].multisample), + ...kRegularTextureFormats.filter(f => kTextureFormatInfo[f].multisample), + ...kDepthStencilFormats.filter(f => kTextureFormatInfo[f].multisample), 'rg11b10ufloat', ] as const; // Texture formats that may possibly be color renderable. // Some may require certain features to be enabled. export const kPossibleColorRenderableTextureFormats = [ - ...kAllTextureFormats.filter(f => kTextureFormatInfo[f].colorRender), + ...kRegularTextureFormats.filter(f => kTextureFormatInfo[f].colorRender), 'rg11b10ufloat', ] as const; export type PossibleColorRenderTextureFormat = (typeof kPossibleColorRenderableTextureFormats)[number]; // Texture formats that have a different base format. This is effectively all -srgb formats. -export const kDifferentBaseFormatTextureFormats = kAllTextureFormats.filter( +export const kDifferentBaseFormatTextureFormats = kColorTextureFormats.filter( f => kTextureFormatInfo[f].baseFormat && kTextureFormatInfo[f].baseFormat !== f ); @@ -1933,6 +1934,14 @@ export function isTextureFormatUsableAsRenderAttachment( return kTextureFormatInfo[format].colorRender || isDepthOrStencilTextureFormat(format); } +/** + * Returns the texture's type (float, unsigned-float, sint, uint, depth) + */ +export function getTextureFormatType(format: GPUTextureFormat) { + const info = kTextureFormatInfo[format]; + return info.color?.type ?? info.depth?.type ?? info.stencil?.type; +} + /** * Returns if a texture can be used as a "colorAttachment". */ diff --git a/src/webgpu/gpu_test.ts b/src/webgpu/gpu_test.ts index 3b6968ff93a0..82e308917c28 100644 --- a/src/webgpu/gpu_test.ts +++ b/src/webgpu/gpu_test.ts @@ -37,6 +37,7 @@ import { isTextureFormatUsableAsStorageFormat, isTextureFormatUsableAsRenderAttachment, isTextureFormatMultisampled, + is32Float, } from './format_info.js'; import { checkElementsEqual, checkElementsBetween } from './util/check_contents.js'; import { CommandBufferMaker, EncoderType } from './util/command_buffer_maker.js'; @@ -669,6 +670,18 @@ export class GPUTestBase extends Fixture { } } + skipIfTextureFormatNotBlendable(...formats: (GPUTextureFormat | undefined)[]) { + for (const format of formats) { + if (format === undefined) continue; + if (is32Float(format)) { + this.skipIf( + !this.device.features.has('float32-blendable'), + `texture format '${format}' is not blendable` + ); + } + } + } + skipIfTextureFormatDoesNotSupportUsage( usage: GPUTextureUsageFlags, ...formats: (GPUTextureFormat | undefined)[]