export const description = `
Tests for GPUCanvasContext.configure.
TODO:
- Test colorSpace
- Test toneMapping
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { assert } from '../../../common/util/util.js';
import {
isValidTextureUsageCombination,
kCanvasTextureFormats,
kSomeBogusTextureUsage,
kTextureUsages,
kValidCombinationsOfOneOrTwoTextureUsages,
usageIsTypeErrorForConfigure,
} from '../../capability_info.js';
import { GPUConst } from '../../constants.js';
import {
kAllTextureFormats,
kFeaturesForFormats,
filterFormatsByFeature,
textureFormatsAreViewCompatible,
} from '../../format_info.js';
import { AllFeaturesMaxLimitsGPUTest } from '../../gpu_test.js';
import { kAllCanvasTypes, createCanvas } from '../../util/create_elements.js';
export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
g.test('defaults')
.desc(
`
Ensure that the defaults for GPUCanvasConfiguration are correct.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
)
.fn(t => {
const { canvasType } = t.params;
const canvas = createCanvas(t, canvasType, 2, 2);
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
ctx.configure({
device: t.device,
format: 'rgba8unorm',
});
const configuration = ctx.getConfiguration();
assert(configuration !== null);
t.expect(configuration.device === t.device);
t.expect(configuration.format === 'rgba8unorm');
t.expect(configuration.usage === GPUTextureUsage.RENDER_ATTACHMENT);
t.expect(configuration.viewFormats.length === 0);
t.expect(configuration.colorSpace === 'srgb');
t.expect(configuration.toneMapping?.mode === 'standard');
t.expect(configuration.alphaMode === 'opaque');
const currentTexture = ctx.getCurrentTexture();
t.expect(currentTexture.format === 'rgba8unorm');
t.expect(currentTexture.usage === GPUTextureUsage.RENDER_ATTACHMENT);
t.expect(currentTexture.dimension === '2d');
t.expect(currentTexture.width === canvas.width);
t.expect(currentTexture.height === canvas.height);
t.expect(currentTexture.depthOrArrayLayers === 1);
t.expect(currentTexture.mipLevelCount === 1);
t.expect(currentTexture.sampleCount === 1);
});
g.test('device')
.desc(
`
Ensure that configure reacts appropriately to various device states.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
)
.fn(t => {
const { canvasType } = t.params;
const canvas = createCanvas(t, canvasType, 2, 2);
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
t.expect(ctx.getConfiguration() === null);
t.shouldThrow('TypeError', () => {
ctx.configure({
format: 'rgba8unorm',
} as GPUCanvasConfiguration);
});
t.shouldThrow('InvalidStateError', () => {
ctx.getCurrentTexture();
});
t.shouldThrow('InvalidStateError', () => {
ctx.getCurrentTexture();
});
ctx.configure({
device: t.device,
format: 'rgba8unorm',
alphaMode: 'opaque',
});
const configuration = ctx.getConfiguration();
assert(configuration !== null);
t.expect(configuration.device === t.device);
t.expect(configuration.format === 'rgba8unorm');
t.expect(configuration.usage === GPUTextureUsage.RENDER_ATTACHMENT);
t.expect(configuration.viewFormats.length === 0);
t.expect(configuration.colorSpace === 'srgb');
t.expect(configuration.toneMapping?.mode === 'standard');
t.expect(configuration.alphaMode === 'opaque');
ctx.getCurrentTexture();
ctx.unconfigure();
t.shouldThrow('InvalidStateError', () => {
ctx.getCurrentTexture();
});
t.expect(ctx.getConfiguration() === null);
ctx.configure({
device: t.device,
format: 'rgba8unorm',
alphaMode: 'premultiplied',
});
ctx.getCurrentTexture();
const newConfiguration = ctx.getConfiguration();
assert(newConfiguration !== null);
t.expect(newConfiguration.device === t.device);
t.expect(newConfiguration.format === 'rgba8unorm');
t.expect(newConfiguration.usage === GPUTextureUsage.RENDER_ATTACHMENT);
t.expect(newConfiguration.viewFormats.length === 0);
t.expect(newConfiguration.colorSpace === 'srgb');
t.expect(newConfiguration.toneMapping?.mode === 'standard');
t.expect(newConfiguration.alphaMode === 'premultiplied');
});
g.test('format')
.desc(
`
Ensure that only valid texture formats are allowed when calling configure.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('format', kAllTextureFormats)
)
.fn(t => {
const { canvasType, format } = t.params;
t.skipIfTextureFormatNotSupported(format);
const canvas = createCanvas(t, canvasType, 2, 2);
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
let validFormat = false;
for (const canvasFormat of kCanvasTextureFormats) {
if (format === canvasFormat) {
validFormat = true;
break;
}
}
if (validFormat) {
ctx.configure({
device: t.device,
format,
});
const configuration = ctx.getConfiguration();
t.expect(configuration!.format === format);
} else {
t.shouldThrow('TypeError', () => {
ctx.configure({
device: t.device,
format,
});
});
}
});
g.test('usage')
.desc(
`
Ensure that getCurrentTexture returns a texture with the configured usages.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.beginSubcases()
.combine(
'usage',
kValidCombinationsOfOneOrTwoTextureUsages
.filter(usage => !usageIsTypeErrorForConfigure(usage))
)
)
.fn(t => {
const { canvasType, usage } = t.params;
if ((usage & GPUConst.TextureUsage.TRANSIENT_ATTACHMENT) !== 0) {
t.skipIfTransientAttachmentNotSupported();
}
const canvas = createCanvas(t, canvasType, 2, 2);
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
ctx.configure({
device: t.device,
format: 'rgba8unorm',
usage,
});
const configuration = ctx.getConfiguration();
t.expect(configuration!.usage === usage);
const currentTexture = ctx.getCurrentTexture();
t.expect(currentTexture instanceof GPUTexture);
t.expect(currentTexture.usage === usage);
if (usage & GPUConst.TextureUsage.RENDER_ATTACHMENT) {
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: currentTexture.createView(),
clearValue: [1.0, 0.0, 0.0, 1.0],
loadOp: 'clear',
storeOp: 'store',
},
],
});
pass.end();
t.device.queue.submit([encoder.finish()]);
}
if (usage & GPUConst.TextureUsage.TEXTURE_BINDING) {
const bgl = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
texture: {},
},
],
});
t.device.createBindGroup({
layout: bgl,
entries: [
{
binding: 0,
resource: currentTexture.createView(),
},
],
});
}
const canUseStorageTextureInFragmentShader =
!t.isCompatibility || t.device.limits.maxStorageTexturesInFragmentStage! > 0;
if (
(usage & GPUConst.TextureUsage.STORAGE_BINDING) !== 0 &&
canUseStorageTextureInFragmentShader
) {
const bgl = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUShaderStage.FRAGMENT,
storageTexture: { access: 'write-only', format: currentTexture.format },
},
],
});
t.device.createBindGroup({
layout: bgl,
entries: [
{
binding: 0,
resource: currentTexture.createView(),
},
],
});
}
if (usage & GPUConst.TextureUsage.COPY_DST) {
const rgbaData = new Uint8Array([255, 0, 0, 255]);
t.device.queue.writeTexture({ texture: currentTexture }, rgbaData, {}, [1, 1, 1]);
}
if (usage & GPUConst.TextureUsage.COPY_SRC) {
const size = [currentTexture.width, currentTexture.height, 1];
const dstTexture = t.createTextureTracked({
format: currentTexture.format,
usage: GPUTextureUsage.COPY_DST,
size,
});
const encoder = t.device.createCommandEncoder();
encoder.copyTextureToTexture({ texture: currentTexture }, { texture: dstTexture }, size);
t.device.queue.submit([encoder.finish()]);
}
});
g.test('invalid_usage')
.desc(`Test what happens when an invalid usage is passed to configure().`)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('format', kCanvasTextureFormats)
.combine('usage', [
0,
GPUConst.TextureUsage.COPY_DST,
GPUConst.TextureUsage.STORAGE_BINDING,
...kTextureUsages.filter(usageIsTypeErrorForConfigure),
GPUConst.TextureUsage.TRANSIENT_ATTACHMENT | GPUConst.TextureUsage.RENDER_ATTACHMENT,
kSomeBogusTextureUsage,
GPUConst.TextureUsage.COPY_DST | kSomeBogusTextureUsage,
GPUConst.TextureUsage.STORAGE_BINDING | kSomeBogusTextureUsage,
GPUConst.TextureUsage.TRANSIENT_ATTACHMENT | kSomeBogusTextureUsage,
])
)
.fn(t => {
const { canvasType, format, usage } = t.params;
if ((usage & GPUConst.TextureUsage.TRANSIENT_ATTACHMENT) !== 0) {
t.skipIfTransientAttachmentNotSupported();
}
const canvas = createCanvas(t, canvasType, 2, 2);
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
if (usageIsTypeErrorForConfigure(usage)) {
t.shouldThrow('TypeError', () => {
ctx.configure({ device: t.device, format, usage });
});
const configuration = ctx.getConfiguration();
assert(configuration === null);
t.shouldThrow('InvalidStateError', () => {
ctx.getCurrentTexture();
});
} else {
const invalidUsage = !isValidTextureUsageCombination(usage);
t.expectValidationError(() => {
ctx.configure({ device: t.device, format, usage });
}, invalidUsage);
const configuration = ctx.getConfiguration();
assert(configuration !== null);
t.expect(configuration.usage === usage);
let currentTexture: GPUTexture | undefined;
t.expectValidationError(() => {
currentTexture = ctx.getCurrentTexture();
}, invalidUsage);
assert(currentTexture !== undefined && currentTexture instanceof GPUTexture);
t.expect(currentTexture.usage === usage);
}
});
g.test('alpha_mode')
.desc(
`
Ensure that all valid alphaMode values are allowed when calling configure.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.beginSubcases()
.combine('alphaMode', ['opaque', 'premultiplied'] as const)
)
.fn(t => {
const { canvasType, alphaMode } = t.params;
const canvas = createCanvas(t, canvasType, 2, 2);
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
ctx.configure({
device: t.device,
format: 'rgba8unorm',
alphaMode,
});
const configuration = ctx.getConfiguration();
t.expect(configuration!.alphaMode === alphaMode);
const currentTexture = ctx.getCurrentTexture();
t.expect(currentTexture instanceof GPUTexture);
});
g.test('size_zero_before_configure')
.desc(`Ensure a validation error is raised in configure() if the size of the canvas is zero.`)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('zeroDimension', ['width', 'height'] as const)
)
.fn(t => {
const { canvasType, zeroDimension } = t.params;
const canvas = createCanvas(t, canvasType, 1, 1);
canvas[zeroDimension] = 0;
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
t.expectValidationError(() => {
ctx.configure({
device: t.device,
format: 'bgra8unorm',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
});
canvas[zeroDimension] = 1;
const currentTexture = ctx.getCurrentTexture();
const encoder = t.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: currentTexture.createView(),
clearValue: [1.0, 0.0, 0.0, 1.0],
loadOp: 'clear',
storeOp: 'store',
},
],
});
pass.end();
t.device.queue.submit([encoder.finish()]);
});
g.test('size_zero_after_configure')
.desc(
`Ensure a validation error is raised after configure() if the size of the canvas becomes zero.`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('zeroDimension', ['width', 'height'] as const)
)
.fn(t => {
const { canvasType, zeroDimension } = t.params;
const canvas = createCanvas(t, canvasType, 1, 1);
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
ctx.configure({
device: t.device,
format: 'bgra8unorm',
usage: GPUTextureUsage.RENDER_ATTACHMENT,
});
canvas[zeroDimension] = 0;
let currentTexture: GPUTexture;
t.expectValidationError(() => {
currentTexture = ctx.getCurrentTexture();
});
t.expect(currentTexture![zeroDimension] === 0);
t.expectValidationError(() => {
currentTexture.createView();
});
});
g.test('viewFormats')
.desc(
`Test the validation that viewFormats are compatible with the format (for all canvas format / view formats)`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('format', kCanvasTextureFormats)
.combine('viewFormatFeature', kFeaturesForFormats)
.beginSubcases()
.expand('viewFormat', ({ viewFormatFeature }) =>
filterFormatsByFeature(viewFormatFeature, kAllTextureFormats)
)
)
.fn(t => {
const { canvasType, format, viewFormat } = t.params;
t.skipIfTextureFormatNotSupported(viewFormat);
const canvas = createCanvas(t, canvasType, 1, 1);
const ctx = canvas.getContext('webgpu');
assert(ctx instanceof GPUCanvasContext, 'Failed to get WebGPU context from canvas');
const compatible = textureFormatsAreViewCompatible(t.device.features, format, viewFormat);
t.expectValidationError(() => {
ctx.configure({
device: t.device,
format,
viewFormats: [viewFormat],
});
}, !compatible);
const viewFormats = ctx.getConfiguration()!.viewFormats;
t.expect(viewFormats[0] === viewFormat);
let currentTexture: GPUTexture;
t.expectValidationError(() => {
currentTexture = ctx.getCurrentTexture();
}, !compatible);
t.expectValidationError(() => {
currentTexture.createView();
}, !compatible);
});