Skip to content

Commit

Permalink
WGSL textureSample tests for 3D and cubemaps (#3782)
Browse files Browse the repository at this point in the history
* WGSL textureSample tests for 3D and cubemaps

Note: 3D compressed textures are not tested as they
are not yet supported in any implementation.

Also, see internal comments on texture corners.
  • Loading branch information
greggman authored Jun 10, 2024
1 parent 5aab3c2 commit cb42984
Show file tree
Hide file tree
Showing 5 changed files with 922 additions and 128 deletions.
2 changes: 1 addition & 1 deletion src/webgpu/listing_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -866,8 +866,8 @@
"webgpu:compat,api,validation,shader_module,shader_module:interpolate:*": { "subcaseMS": 3.488 },
"webgpu:compat,api,validation,shader_module,shader_module:sample_index:*": { "subcaseMS": 0.487 },
"webgpu:compat,api,validation,shader_module,shader_module:sample_mask:*": { "subcaseMS": 0.408 },
"webgpu:compat,api,validation,shader_module,shader_module:unsupportedStorageTextureFormats:*": { "subcaseMS": 1.206 },
"webgpu:compat,api,validation,shader_module,shader_module:textureLoad_with_depth_textures:*": { "subcaseMS": 1.259 },
"webgpu:compat,api,validation,shader_module,shader_module:unsupportedStorageTextureFormats:*": { "subcaseMS": 1.206 },
"webgpu:compat,api,validation,texture,createTexture:depthOrArrayLayers_incompatible_with_textureBindingViewDimension:*": { "subcaseMS": 12.712 },
"webgpu:compat,api,validation,texture,createTexture:format_reinterpretation:*": { "subcaseMS": 7.012 },
"webgpu:compat,api,validation,texture,createTexture:invalidTextureBindingViewDimension:*": { "subcaseMS": 6.022 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,62 @@ note: uniformity validation is covered in src/webgpu/shader/validation/uniformit
`;

import { makeTestGroup } from '../../../../../../common/framework/test_group.js';
import { unreachable } from '../../../../../../common/util/util.js';
import {
isCompressedTextureFormat,
kCompressedTextureFormats,
kEncodableTextureFormats,
kTextureFormatInfo,
} from '../../../../../format_info.js';
import { GPUTest, TextureTestMixin } from '../../../../../gpu_test.js';
import { align, hashU32 } from '../../../../../util/math.js';
import { hashU32 } from '../../../../../util/math.js';

import {
vec2,
vec3,
TextureCall,
putDataInTextureThenDrawAndCheckResultsComparedToSoftwareRasterizer,
generateSamplePoints,
generateSamplePoints2D,
generateSamplePoints3D,
kSamplePointMethods,
doTextureCalls,
checkCallResults,
createTextureWithRandomDataAndGetTexels,
generateSamplePointsCube,
kCubeSamplePointMethods,
SamplePointMethods,
chooseTextureSize,
} from './texture_utils.js';
import { generateCoordBoundaries, generateOffsets } from './utils.js';

const kTestableColorFormats = [...kEncodableTextureFormats, ...kCompressedTextureFormats] as const;

function getDepthOrArrayLayersForViewDimension(viewDimension: GPUTextureViewDimension) {
switch (viewDimension) {
case '2d':
return 1;
case '3d':
return 8;
case 'cube':
return 6;
default:
unreachable();
}
}

function getTextureTypeForTextureViewDimension(viewDimension: GPUTextureViewDimension) {
switch (viewDimension) {
case '2d':
return 'texture_2d<f32>';
case '3d':
return 'texture_3d<f32>';
case 'cube':
return 'texture_cube<f32>';
default:
unreachable();
}
}

export const g = makeTestGroup(TextureTestMixin(GPUTest));

g.test('sampled_1d_coords')
Expand Down Expand Up @@ -101,9 +134,7 @@ Parameters:
const { format, sample_points, addressModeU, addressModeV, minFilter, offset } = t.params;

// We want at least 4 blocks or something wide enough for 3 mip levels.
const { blockWidth, blockHeight } = kTextureFormatInfo[format];
const width = align(Math.max(8, blockWidth * 4), blockWidth);
const height = blockHeight * 4;
const [width, height] = chooseTextureSize({ minSize: 8, minBlocks: 4, format });

const descriptor: GPUTextureDescriptor = {
format,
Expand All @@ -112,17 +143,17 @@ Parameters:
};
const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

const calls: TextureCall<vec2>[] = generateSamplePoints(50, minFilter === 'nearest', {
const calls: TextureCall<vec2>[] = generateSamplePoints2D(50, minFilter === 'nearest', {
method: sample_points,
textureWidth: texture.width,
textureHeight: texture.height,
}).map((c, i) => {
const hash = hashU32(i) & 0xff;
const hash = hashU32(i);
return {
builtin: 'textureSample',
coordType: 'f',
coords: c,
offset: offset ? [(hash & 15) - 8, (hash >> 4) - 8] : undefined,
offset: offset ? [(hash & 0xf) - 8, ((hash >> 4) & 0xf) - 8] : undefined,
};
});
const sampler: GPUSamplerDescriptor = {
Expand All @@ -131,8 +162,23 @@ Parameters:
minFilter,
magFilter: minFilter,
};
const results = await doTextureCalls(t.device, texture, sampler, calls);
const res = await checkCallResults(device, { texels, descriptor }, sampler, calls, results);
const viewDescriptor = {};
const results = await doTextureCalls(
t.device,
texture,
viewDescriptor,
'texture_2d<f32>',
sampler,
calls
);
const res = await checkCallResults(
device,
{ texels, descriptor, viewDescriptor },
'texture_2d<f32>',
sampler,
calls,
results
);
t.expectOK(res);
});

Expand Down Expand Up @@ -189,9 +235,7 @@ test mip level selection based on derivatives
const { format, mipmapFilter, ddx, ddy, uvwStart, offset } = t.params;

// We want at least 4 blocks or something wide enough for 3 mip levels.
const { blockWidth, blockHeight } = kTextureFormatInfo[format];
const width = align(Math.max(8, blockWidth * 4), blockWidth);
const height = blockHeight * 4;
const [width, height] = chooseTextureSize({ minSize: 8, minBlocks: 4, format });

const descriptor: GPUTextureDescriptor = {
format,
Expand All @@ -207,9 +251,11 @@ test mip level selection based on derivatives
magFilter: 'linear',
mipmapFilter,
};
const viewDescriptor = {};
await putDataInTextureThenDrawAndCheckResultsComparedToSoftwareRasterizer(
t,
descriptor,
viewDescriptor,
sampler,
{ ddx, ddy, uvwStart, offset }
);
Expand All @@ -233,17 +279,112 @@ Parameters:
* The offset expression must be a creation-time expression (e.g. vec2<i32>(1, 2)).
* Each offset component must be at least -8 and at most 7.
Values outside of this range will result in a shader-creation error.
* TODO: test 3d compressed textures formats. Just remove the filter below 'viewDimension'
`
)
.params(u =>
u
.combine('texture_type', ['texture_3d', 'texture_cube'] as const)
.combine('format', kTestableColorFormats)
.filter(t => {
const type = kTextureFormatInfo[t.format].color?.type;
const canPotentialFilter = type === 'float' || type === 'unfilterable-float';
// We can't easily put random bytes into compressed textures if they are float formats
// since we want the range to be +/- 1000 and not +/- infinity or NaN.
const isFillable = !isCompressedTextureFormat(t.format) || !t.format.endsWith('float');
return canPotentialFilter && isFillable;
})
.combine('viewDimension', ['3d', 'cube'] as const)
.filter(t => !isCompressedTextureFormat(t.format) || t.viewDimension === 'cube')
.combine('sample_points', kCubeSamplePointMethods)
.filter(t => t.sample_points !== 'cube-edges' || t.viewDimension !== '3d')
.beginSubcases()
.combine('S', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
.combine('coords', generateCoordBoundaries(3))
.combine('offset', generateOffsets(3))
.combine('addressModeU', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
.combine('addressModeV', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
.combine('addressModeW', ['clamp-to-edge', 'repeat', 'mirror-repeat'] as const)
.combine('minFilter', ['nearest', 'linear'] as const)
.combine('offset', [false, true] as const)
.filter(t => t.viewDimension !== 'cube' || t.offset !== true)
)
.unimplemented();
.beforeAllSubcases(t => {
const { format } = t.params;
t.skipIfTextureFormatNotSupported(format);
const info = kTextureFormatInfo[format];
if (info.color?.type === 'unfilterable-float') {
t.selectDeviceOrSkipTestCase('float32-filterable');
} else {
t.selectDeviceForTextureFormatOrSkipTestCase(t.params.format);
}
})
.fn(async t => {
const device = t.device;
const { format, viewDimension, sample_points, addressModeU, addressModeV, minFilter, offset } =
t.params;

const [width, height] = chooseTextureSize({ minSize: 8, minBlocks: 2, format, viewDimension });
const depthOrArrayLayers = getDepthOrArrayLayersForViewDimension(viewDimension);

const descriptor: GPUTextureDescriptor = {
format,
dimension: viewDimension === '3d' ? '3d' : '2d',
...(t.isCompatibility && { textureBindingViewDimension: viewDimension }),
size: { width, height, depthOrArrayLayers },
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING,
};
const { texels, texture } = await createTextureWithRandomDataAndGetTexels(t, descriptor);

const calls: TextureCall<vec3>[] = (
viewDimension === '3d'
? generateSamplePoints3D(50, minFilter === 'nearest', {
method: sample_points as SamplePointMethods,
textureWidth: texture.width,
textureHeight: texture.height,
textureDepthOrArrayLayers: texture.depthOrArrayLayers,
})
: generateSamplePointsCube(50, minFilter === 'nearest', {
method: sample_points,
textureWidth: texture.width,
textureDepthOrArrayLayers: texture.depthOrArrayLayers,
})
).map((c, i) => {
const hash = hashU32(i);
return {
builtin: 'textureSample',
coordType: 'f',
coords: c,
offset: offset
? [(hash & 0xf) - 8, ((hash >> 4) & 0xf) - 8, ((hash >> 8) & 0xf) - 8]
: undefined,
};
});
const sampler: GPUSamplerDescriptor = {
addressModeU,
addressModeV,
minFilter,
magFilter: minFilter,
};
const viewDescriptor = {
dimension: viewDimension,
};
const textureType = getTextureTypeForTextureViewDimension(viewDimension);
const results = await doTextureCalls(
t.device,
texture,
viewDescriptor,
textureType,
sampler,
calls
);
const res = await checkCallResults(
device,
{ texels, descriptor, viewDescriptor },
textureType,
sampler,
calls,
results
);
t.expectOK(res);
});

g.test('depth_2d_coords')
.specURL('https://www.w3.org/TR/WGSL/#texturesample')
Expand Down
Loading

0 comments on commit cb42984

Please sign in to comment.