Skip to content

Commit 452bd98

Browse files
authored
Fix texture-component-swizzle typo (#4498)
This turned into a bigger change than just fixing the typo. With the typo fixed, a bunch of tests started failing. One issue in particular is that out of bounds `textureLoad` needs to take swizzle into account since its allowed to return any value inside the texture. I'm making the assuption that the spec would say it can return any swizzled value in the texture. This comes up with depth textures because if texture-component-swizzle is enabled then we check RGBA for depth textures instead of just R and we expcet GBA to be 001. Before this change, if the texel address was invalid we'd get a texel from the software texture and GBA would be wrong and so no texels would match. Theoretically, with this change, the software sampler code supports swizzles but, that path is not really be exercised. Only the fact that depth textures go from R??? to R001 is used.
1 parent 4fe9eec commit 452bd98

4 files changed

Lines changed: 122 additions & 67 deletions

File tree

src/webgpu/api/operation/texture_view/texture_component_swizzle.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
isBuiltinComparison,
2929
isBuiltinGather,
3030
isFillable,
31+
swizzleTexel,
3132
TextureBuiltin,
3233
} from '../../../shader/execution/expression/call/builtin/texture_utils.js';
3334
import * as ttu from '../../../texture_test_utils.js';
@@ -36,7 +37,6 @@ import { TexelView } from '../../../util/texture/texel_view.js';
3637
import {
3738
kSwizzleTests,
3839
SwizzleSpec,
39-
swizzleTexel,
4040
} from '../../validation/capability_checks/features/texture_component_swizzle_utils.js';
4141

4242
type TextureInput =
Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { PerTexelComponent } from '../../../../util/texture/texel_data.js';
2-
31
declare global {
42
type GPUComponentSwizzle = 'r' | 'g' | 'b' | 'a' | '0' | '1';
53

@@ -31,46 +29,7 @@ export const kSwizzleTests = [
3129
] as const;
3230
export type SwizzleSpec = (typeof kSwizzleTests)[number];
3331

34-
function swizzleComponentToTexelComponent(
35-
src: PerTexelComponent<number>,
36-
component: GPUComponentSwizzle
37-
): number {
38-
switch (component) {
39-
case '0':
40-
return 0;
41-
case '1':
42-
return 1;
43-
case 'r':
44-
return src.R!;
45-
case 'g':
46-
return src.G!;
47-
case 'b':
48-
return src.B!;
49-
case 'a':
50-
return src.A!;
51-
}
52-
}
53-
54-
export function swizzleTexel(
55-
src: PerTexelComponent<number>,
56-
swizzle: GPUTextureComponentSwizzle
57-
): PerTexelComponent<number> {
58-
return {
59-
R: swizzle[0]
60-
? swizzleComponentToTexelComponent(src, swizzle[0] as GPUComponentSwizzle)
61-
: src.R,
62-
G: swizzle[1]
63-
? swizzleComponentToTexelComponent(src, swizzle[1] as GPUComponentSwizzle)
64-
: src.G,
65-
B: swizzle[2]
66-
? swizzleComponentToTexelComponent(src, swizzle[2] as GPUComponentSwizzle)
67-
: src.B,
68-
A: swizzle[3]
69-
? swizzleComponentToTexelComponent(src, swizzle[3] as GPUComponentSwizzle)
70-
: src.A,
71-
};
72-
}
73-
74-
export function isIdentitySwizzle(swizzle: GPUTextureComponentSwizzle): boolean {
75-
return swizzle === 'rgba';
32+
// Returns true if swizzle is identity
33+
export function isIdentitySwizzle(swizzle: GPUTextureComponentSwizzle | undefined): boolean {
34+
return swizzle === undefined || swizzle === 'rgba';
7635
}

src/webgpu/shader/execution/expression/call/builtin/texture_utils.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Tests for texture_utils.ts
55
import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
66
import { assert } from '../../../../../../common/util/util.js';
77
import {
8+
getTextureFormatType,
89
isTextureFormatPossiblyMultisampled,
910
kDepthStencilFormats,
1011
} from '../../../../../format_info.js';
@@ -124,6 +125,16 @@ g.test('readTextureToTexelViews')
124125

125126
assert(actualTexelViews.length === expectedTexelViews.length, 'num mip levels match');
126127

128+
const type = getTextureFormatType(srcFormat, 'all');
129+
const textureType =
130+
type === 'depth'
131+
? 'texture_depth_2d'
132+
: type === 'uint'
133+
? 'texture_2d<u32>'
134+
: type === 'sint'
135+
? 'texture_2d<i32>'
136+
: 'texture_2d<f32>';
137+
127138
const errors = [];
128139
for (let mipLevel = 0; mipLevel < actualTexelViews.length; ++mipLevel) {
129140
const actualMipLevelTexelView = actualTexelViews[mipLevel];
@@ -156,6 +167,8 @@ g.test('readTextureToTexelViews')
156167
if (
157168
!texelsApproximatelyEqual(
158169
t.device,
170+
'textureLoad',
171+
textureType,
159172
actualRGBA,
160173
actualMipLevelTexelView.format,
161174
expectedRGBA,

src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts

Lines changed: 105 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,8 @@ export const builtinNeedsDerivatives = (builtin: TextureBuiltin) =>
14011401
builtin === 'textureSample' ||
14021402
builtin === 'textureSampleBias' ||
14031403
builtin === 'textureSampleCompare';
1404+
// This returns true for `texture_depth_2d`, `texture_depth_cube` etc...
1405+
const isSingleChannelInput = (textureType: string) => textureType.startsWith('texture_depth');
14041406

14051407
const isCubeViewDimension = (viewDescriptor?: GPUTextureViewDescriptor) =>
14061408
viewDescriptor?.dimension === 'cube' || viewDescriptor?.dimension === 'cube-array';
@@ -1550,6 +1552,39 @@ export function convertPerTexelComponentToResultFormat(
15501552
return out;
15511553
}
15521554

1555+
function swizzleComponentToTexelComponent(
1556+
src: PerTexelComponent<number>,
1557+
component: GPUComponentSwizzle
1558+
): number {
1559+
switch (component) {
1560+
case '0':
1561+
return 0;
1562+
case '1':
1563+
return 1;
1564+
case 'r':
1565+
return src.R!;
1566+
case 'g':
1567+
return src.G!;
1568+
case 'b':
1569+
return src.B!;
1570+
case 'a':
1571+
return src.A!;
1572+
}
1573+
}
1574+
1575+
export function swizzleTexel(
1576+
src: PerTexelComponent<number>,
1577+
swizzle: GPUTextureComponentSwizzle | undefined
1578+
): PerTexelComponent<number> {
1579+
swizzle = swizzle ?? 'rgba';
1580+
return {
1581+
R: swizzleComponentToTexelComponent(src, swizzle[0] as GPUComponentSwizzle),
1582+
G: swizzleComponentToTexelComponent(src, swizzle[1] as GPUComponentSwizzle),
1583+
B: swizzleComponentToTexelComponent(src, swizzle[2] as GPUComponentSwizzle),
1584+
A: swizzleComponentToTexelComponent(src, swizzle[3] as GPUComponentSwizzle),
1585+
};
1586+
}
1587+
15531588
/**
15541589
* Convert RGBA result format to texel view format.
15551590
* Example, converts
@@ -1625,7 +1660,7 @@ const kDefaultValueForDepthTextureComponents: Record<TexelComponent, number> = {
16251660
} as const;
16261661

16271662
/**
1628-
* Applies a comparison function to each component of a texel.
1663+
* Applies a comparison function the R or Depth component of a texel.
16291664
*/
16301665
export function applyCompareToTexel(
16311666
components: TexelComponent[],
@@ -1686,6 +1721,7 @@ function getEffectiveLodClamp(
16861721
* mip level
16871722
*/
16881723
function softwareTextureReadMipLevel<T extends Dimensionality>(
1724+
t: GPUTest,
16891725
call: TextureCall<T>,
16901726
softwareTexture: SoftwareTexture,
16911727
sampler: GPUSamplerDescriptor | undefined,
@@ -1701,6 +1737,7 @@ function softwareTextureReadMipLevel<T extends Dimensionality>(
17011737
baseMipLevelSize,
17021738
mipLevel
17031739
);
1740+
const swizzle = softwareTexture.viewDescriptor.swizzle;
17041741

17051742
const addressMode: GPUAddressMode[] =
17061743
call.builtin === 'textureSampleBaseClampToEdge'
@@ -1890,7 +1927,8 @@ function softwareTextureReadMipLevel<T extends Dimensionality>(
18901927
const v = load(c);
18911928
const postV = applyCompare(call, sampler, rep.componentOrder, v);
18921929
const rgba = convertPerTexelComponentToResultFormat(postV, format);
1893-
out[kRGBAComponents[i]] = rgba[component];
1930+
const swizzled = swizzleTexel(rgba, swizzle);
1931+
out[kRGBAComponents[i]] = swizzled[component];
18941932
});
18951933
return out;
18961934
}
@@ -1907,13 +1945,15 @@ function softwareTextureReadMipLevel<T extends Dimensionality>(
19071945
}
19081946
}
19091947

1910-
return convertPerTexelComponentToResultFormat(out, format);
1948+
const rgba = convertPerTexelComponentToResultFormat(out, format);
1949+
return swizzleTexel(rgba, swizzle);
19111950
}
19121951
case 'textureLoad': {
19131952
const out: PerTexelComponent<number> = isOutOfBoundsCall(softwareTexture, call)
19141953
? zeroValuePerTexelComponent(rep.componentOrder)
19151954
: load(call.coords!);
1916-
return convertPerTexelComponentToResultFormat(out, format);
1955+
const rgba = convertPerTexelComponentToResultFormat(out, format);
1956+
return swizzleTexel(rgba, swizzle);
19171957
}
19181958
default:
19191959
unreachable();
@@ -1932,7 +1972,7 @@ function softwareTextureReadLevel<T extends Dimensionality>(
19321972
mipLevel: number
19331973
): PerTexelComponent<number> {
19341974
if (!sampler) {
1935-
return softwareTextureReadMipLevel<T>(call, softwareTexture, sampler, mipLevel);
1975+
return softwareTextureReadMipLevel<T>(t, call, softwareTexture, sampler, mipLevel);
19361976
}
19371977

19381978
const { mipLevelCount } = getBaseMipLevelInfo(softwareTexture);
@@ -1943,8 +1983,8 @@ function softwareTextureReadLevel<T extends Dimensionality>(
19431983
const clampedMipLevel = clamp(mipLevel, lodClampMinMax);
19441984
const rootMipLevel = Math.floor(clampedMipLevel);
19451985
const nextMipLevel = Math.ceil(clampedMipLevel);
1946-
const t0 = softwareTextureReadMipLevel<T>(call, softwareTexture, sampler, rootMipLevel);
1947-
const t1 = softwareTextureReadMipLevel<T>(call, softwareTexture, sampler, nextMipLevel);
1986+
const t0 = softwareTextureReadMipLevel<T>(t, call, softwareTexture, sampler, rootMipLevel);
1987+
const t1 = softwareTextureReadMipLevel<T>(t, call, softwareTexture, sampler, nextMipLevel);
19481988
const weightType = call.builtin === 'textureSampleLevel' ? 'sampleLevelWeights' : 'identity';
19491989
const mix = getWeightForMipLevel(t, stage, weightType, mipLevelCount, clampedMipLevel);
19501990
assert(mix >= 0 && mix <= 1);
@@ -1962,7 +2002,7 @@ function softwareTextureReadLevel<T extends Dimensionality>(
19622002
}
19632003
default: {
19642004
const baseMipLevel = Math.floor(clamp(mipLevel, lodClampMinMax) + 0.5);
1965-
return softwareTextureReadMipLevel<T>(call, softwareTexture, sampler, baseMipLevel);
2005+
return softwareTextureReadMipLevel<T>(t, call, softwareTexture, sampler, baseMipLevel);
19662006
}
19672007
}
19682008
}
@@ -2178,6 +2218,8 @@ function isOutOfBoundsCall<T extends Dimensionality>(
21782218
function isValidOutOfBoundsValue(
21792219
device: GPUDevice,
21802220
softwareTexture: SoftwareTexture,
2221+
builtin: TextureBuiltin,
2222+
textureType: string,
21812223
gotRGBA: PerTexelComponent<number>,
21822224
maxFractionalDiff: number
21832225
) {
@@ -2203,6 +2245,7 @@ function isValidOutOfBoundsValue(
22032245
}
22042246

22052247
// Can be any texel value
2248+
const swizzle = softwareTexture.viewDescriptor.swizzle;
22062249
for (let mipLevel = 0; mipLevel < softwareTexture.texels.length; ++mipLevel) {
22072250
const mipTexels = softwareTexture.texels[mipLevel];
22082251
const size = virtualMipSize(
@@ -2216,10 +2259,15 @@ function isValidOutOfBoundsValue(
22162259
for (let x = 0; x < size[0]; ++x) {
22172260
for (let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
22182261
const texel = mipTexels.color({ x, y, z, sampleIndex });
2219-
const rgba = convertPerTexelComponentToResultFormat(texel, mipTexels.format);
2262+
const rgba = swizzleTexel(
2263+
convertPerTexelComponentToResultFormat(texel, mipTexels.format),
2264+
swizzle
2265+
);
22202266
if (
22212267
texelsApproximatelyEqual(
22222268
device,
2269+
builtin,
2270+
textureType,
22232271
gotRGBA,
22242272
softwareTexture.descriptor.format,
22252273
rgba,
@@ -2250,14 +2298,22 @@ function okBecauseOutOfBounds<T extends Dimensionality>(
22502298
device: GPUDevice,
22512299
softwareTexture: SoftwareTexture,
22522300
call: TextureCall<T>,
2301+
textureType: string,
22532302
gotRGBA: PerTexelComponent<number>,
22542303
maxFractionalDiff: number
22552304
) {
22562305
if (!isOutOfBoundsCall(softwareTexture, call)) {
22572306
return false;
22582307
}
22592308

2260-
return isValidOutOfBoundsValue(device, softwareTexture, gotRGBA, maxFractionalDiff);
2309+
return isValidOutOfBoundsValue(
2310+
device,
2311+
softwareTexture,
2312+
call.builtin,
2313+
textureType,
2314+
gotRGBA,
2315+
maxFractionalDiff
2316+
);
22612317
}
22622318

22632319
const kRGBAComponents = [
@@ -2274,6 +2330,8 @@ const kRComponent = [TexelComponent.R] as const;
22742330
*/
22752331
export function texelsApproximatelyEqual(
22762332
device: GPUDevice,
2333+
builtin: TextureBuiltin,
2334+
textureType: string,
22772335
gotRGBA: PerTexelComponent<number>,
22782336
gotFormat: GPUTextureFormat,
22792337
expectRGBA: PerTexelComponent<number>,
@@ -2292,11 +2350,7 @@ export function texelsApproximatelyEqual(
22922350
expectedFormat
22932351
);
22942352

2295-
const rgbaComponentsToCheck =
2296-
isDepthOrStencilTextureFormat(gotFormat) && !device.features.has('texel-component-swizzle')
2297-
? kRComponent
2298-
: kRGBAComponents;
2299-
2353+
const rgbaComponentsToCheck = getComponentsToCheck(device, gotFormat, builtin, textureType);
23002354
for (const component of rgbaComponentsToCheck) {
23012355
const g = gotRGBA[component]!;
23022356
const e = expectRGBA[component]!;
@@ -2371,6 +2425,27 @@ baseMipLevelSize: [${baseMipLevelSize.join(', ')}]
23712425
physicalMipCount: ${physicalMipLevelCount}
23722426
`;
23732427
}
2428+
2429+
function getComponentsToCheck(
2430+
device: GPUDevice,
2431+
format: GPUTextureFormat,
2432+
builtin: TextureBuiltin,
2433+
textureType: string
2434+
) {
2435+
const returnsOneComponent = !isBuiltinGather(builtin) && isSingleChannelInput(textureType);
2436+
if (returnsOneComponent) {
2437+
return kRComponent;
2438+
}
2439+
2440+
const gbaUndefined =
2441+
isDepthOrStencilTextureFormat(format) && !device.features.has('texel-component-swizzle');
2442+
if (gbaUndefined) {
2443+
return kRComponent;
2444+
}
2445+
2446+
return kRGBAComponents;
2447+
}
2448+
23742449
/**
23752450
* Checks the result of each call matches the expected result.
23762451
*/
@@ -2518,6 +2593,8 @@ export async function checkCallResults<T extends Dimensionality>(
25182593
if (
25192594
texelsApproximatelyEqual(
25202595
t.device,
2596+
call.builtin,
2597+
textureType,
25212598
gotRGBA,
25222599
softwareTexture.descriptor.format,
25232600
expectRGBA,
@@ -2530,7 +2607,14 @@ export async function checkCallResults<T extends Dimensionality>(
25302607

25312608
if (
25322609
!sampler &&
2533-
okBecauseOutOfBounds(t.device, softwareTexture, call, gotRGBA, callSpecificMaxFractionalDiff)
2610+
okBecauseOutOfBounds(
2611+
t.device,
2612+
softwareTexture,
2613+
call,
2614+
textureType,
2615+
gotRGBA,
2616+
callSpecificMaxFractionalDiff
2617+
)
25342618
) {
25352619
continue;
25362620
}
@@ -2541,11 +2625,10 @@ export async function checkCallResults<T extends Dimensionality>(
25412625
// from the spec: https://gpuweb.github.io/gpuweb/#reading-depth-stencil
25422626
// depth and stencil values are D, ?, ?, ? unless texture-component-swizzle is enabled
25432627
// in which case it's D, 0, 0, 1
2544-
const rgbaComponentsToCheck =
2545-
(isBuiltinGather(call.builtin) || !isDepthOrStencilTextureFormat(format)) &&
2546-
t.device.features.has('texture-component-swizzle')
2547-
? kRGBAComponents
2548-
: kRComponent;
2628+
//
2629+
// That said, functions that take `texture_depth_??`, except textureGatherCompare, return f32, not vec4f.
2630+
// Our tests convert them to vec4f for reasons but we only care about the first channel.
2631+
const rgbaComponentsToCheck = getComponentsToCheck(t.device, format, call.builtin, textureType);
25492632

25502633
let bad = false;
25512634
const diffs = rgbaComponentsToCheck.map(component => {
@@ -5228,7 +5311,7 @@ ${stageWGSL}
52285311
(sampler.minFilter === 'linear' ||
52295312
sampler.magFilter === 'linear' ||
52305313
sampler.mipmapFilter === 'linear');
5231-
let sampleType: GPUTextureSampleType = textureType.startsWith('texture_depth')
5314+
let sampleType: GPUTextureSampleType = isSingleChannelInput(textureType)
52325315
? 'depth'
52335316
: isDepthTextureFormat(format)
52345317
? 'unfilterable-float'

0 commit comments

Comments
 (0)