summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts362
1 files changed, 362 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
new file mode 100644
index 0000000000..30c8ddd962
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/operation/texture_view/format_reinterpretation.spec.ts
@@ -0,0 +1,362 @@
+export const description = `
+Test texture views can reinterpret the format of the original texture.
+`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import {
+ EncodableTextureFormat,
+ kRenderableColorTextureFormats,
+ kRegularTextureFormats,
+ viewCompatible,
+} from '../../../capability_info.js';
+import { GPUTest } from '../../../gpu_test.js';
+import { TexelView } from '../../../util/texture/texel_view.js';
+import { textureContentIsOKByT2B } from '../../../util/texture/texture_ok.js';
+
+export const g = makeTestGroup(GPUTest);
+
+const kColors = [
+ { R: 1.0, G: 0.0, B: 0.0, A: 0.8 },
+ { R: 0.0, G: 1.0, B: 0.0, A: 0.7 },
+ { R: 0.0, G: 0.0, B: 0.0, A: 0.6 },
+ { R: 0.0, G: 0.0, B: 0.0, A: 0.5 },
+ { R: 1.0, G: 1.0, B: 1.0, A: 0.4 },
+ { R: 0.7, G: 0.0, B: 0.0, A: 0.3 },
+ { R: 0.0, G: 0.8, B: 0.0, A: 0.2 },
+ { R: 0.0, G: 0.0, B: 0.9, A: 0.1 },
+ { R: 0.1, G: 0.2, B: 0.0, A: 0.3 },
+ { R: 0.4, G: 0.3, B: 0.6, A: 0.8 },
+];
+
+const kTextureSize = 16;
+
+function makeInputTexelView(format: EncodableTextureFormat) {
+ return TexelView.fromTexelsAsColors(
+ format,
+ coords => {
+ const pixelPos = coords.y * kTextureSize + coords.x;
+ return kColors[pixelPos % kColors.length];
+ },
+ { clampToFormatRange: true }
+ );
+}
+
+function makeBlitPipeline(
+ device: GPUDevice,
+ format: GPUTextureFormat,
+ multisample: { sample: number; render: number }
+) {
+ return device.createRenderPipeline({
+ layout: 'auto',
+ vertex: {
+ module: device.createShaderModule({
+ code: `
+ @vertex fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4<f32> {
+ var pos = array<vec2<f32>, 6>(
+ vec2<f32>(-1.0, -1.0),
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>( 1.0, -1.0),
+ vec2<f32>(-1.0, 1.0),
+ vec2<f32>( 1.0, -1.0),
+ vec2<f32>( 1.0, 1.0));
+ return vec4<f32>(pos[VertexIndex], 0.0, 1.0);
+ }`,
+ }),
+ entryPoint: 'main',
+ },
+ fragment: {
+ module:
+ multisample.sample > 1
+ ? device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var src: texture_multisampled_2d<f32>;
+ @fragment fn main(@builtin(position) coord: vec4<f32>) -> @location(0) vec4<f32> {
+ var result : vec4<f32>;
+ for (var i = 0; i < ${multisample.sample}; i = i + 1) {
+ result = result + textureLoad(src, vec2<i32>(coord.xy), i);
+ }
+ return result * ${1 / multisample.sample};
+ }`,
+ })
+ : device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var src: texture_2d<f32>;
+ @fragment fn main(@builtin(position) coord: vec4<f32>) -> @location(0) vec4<f32> {
+ return textureLoad(src, vec2<i32>(coord.xy), 0);
+ }`,
+ }),
+ entryPoint: 'main',
+ targets: [{ format }],
+ },
+ multisample: {
+ count: multisample.render,
+ },
+ });
+}
+
+g.test('texture_binding')
+ .desc(`Test that a regular texture allocated as 'format' is correctly sampled as 'viewFormat'.`)
+ .params(u =>
+ u //
+ .combine('format', kRegularTextureFormats)
+ .combine('viewFormat', kRegularTextureFormats)
+ .filter(
+ ({ format, viewFormat }) => format !== viewFormat && viewCompatible(format, viewFormat)
+ )
+ )
+ .fn(async t => {
+ const { format, viewFormat } = t.params;
+
+ // Make an input texel view.
+ const inputTexelView = makeInputTexelView(format);
+
+ // Create the initial texture with the contents if the input texel view.
+ const texture = t.makeTextureWithContents(inputTexelView, {
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ viewFormats: [viewFormat],
+ });
+
+ // Reinterpret the texture as the view format.
+ // Make a texel view of the format that also reinterprets the data.
+ const reinterpretedView = texture.createView({ format: viewFormat });
+ const reinterpretedTexelView = TexelView.fromTexelsAsBytes(viewFormat, inputTexelView.bytes);
+
+ // Create a pipeline to write data out to rgba8unorm.
+ const pipeline = t.device.createComputePipeline({
+ layout: 'auto',
+ compute: {
+ module: t.device.createShaderModule({
+ code: `
+ @group(0) @binding(0) var src: texture_2d<f32>;
+ @group(0) @binding(1) var dst: texture_storage_2d<rgba8unorm, write>;
+ @compute @workgroup_size(1, 1) fn main(
+ @builtin(global_invocation_id) global_id: vec3<u32>,
+ ) {
+ var coord = vec2<i32>(global_id.xy);
+ textureStore(dst, coord, textureLoad(src, coord, 0));
+ }`,
+ }),
+ entryPoint: 'main',
+ },
+ });
+
+ // Create an rgba8unorm output texture.
+ const outputTexture = t.trackForCleanup(
+ t.device.createTexture({
+ format: 'rgba8unorm',
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_SRC,
+ })
+ );
+
+ // Execute a compute pass to load data from the reinterpreted view and
+ // write out to the rgba8unorm texture.
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginComputePass();
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: reinterpretedView,
+ },
+ {
+ binding: 1,
+ resource: outputTexture.createView(),
+ },
+ ],
+ })
+ );
+ pass.dispatchWorkgroups(kTextureSize, kTextureSize);
+ pass.end();
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ const result = await textureContentIsOKByT2B(
+ t,
+ { texture: outputTexture },
+ [kTextureSize, kTextureSize],
+ {
+ expTexelView: TexelView.fromTexelsAsColors('rgba8unorm', reinterpretedTexelView.color, {
+ clampToFormatRange: true,
+ }),
+ },
+ { maxDiffULPsForNormFormat: 1 }
+ );
+ t.expectOK(result);
+ });
+
+g.test('render_and_resolve_attachment')
+ .desc(
+ `Test that a color render attachment allocated as 'format' is correctly rendered to as 'viewFormat',
+and resolved to an attachment allocated as 'format' viewed as 'viewFormat'.
+
+Other combinations aren't possible because the render and resolve targets must both match
+in view format and match in base format.`
+ )
+ .params(u =>
+ u //
+ .combine('format', kRenderableColorTextureFormats)
+ .combine('viewFormat', kRenderableColorTextureFormats)
+ .filter(
+ ({ format, viewFormat }) => format !== viewFormat && viewCompatible(format, viewFormat)
+ )
+ .combine('sampleCount', [1, 4])
+ )
+ .fn(async t => {
+ const { format, viewFormat, sampleCount } = t.params;
+
+ // Make an input texel view.
+ const inputTexelView = makeInputTexelView(format);
+
+ // Create the renderTexture as |format|.
+ const renderTexture = t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [kTextureSize, kTextureSize],
+ usage:
+ GPUTextureUsage.RENDER_ATTACHMENT |
+ (sampleCount > 1 ? GPUTextureUsage.TEXTURE_BINDING : GPUTextureUsage.COPY_SRC),
+ viewFormats: [viewFormat],
+ sampleCount,
+ })
+ );
+
+ const resolveTexture =
+ sampleCount === 1
+ ? undefined
+ : t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ viewFormats: [viewFormat],
+ })
+ );
+
+ // Create the sample source with the contents of the input texel view.
+ // We will sample this texture into |renderTexture|. It uses the same format to keep the same
+ // number of bits of precision.
+ const sampleSource = t.makeTextureWithContents(inputTexelView, {
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.TEXTURE_BINDING,
+ });
+
+ // Reinterpret the renderTexture as |viewFormat|.
+ const reinterpretedRenderView = renderTexture.createView({ format: viewFormat });
+ const reinterpretedResolveView =
+ resolveTexture && resolveTexture.createView({ format: viewFormat });
+
+ // Create a pipeline to blit a src texture to the render attachment.
+ const pipeline = makeBlitPipeline(t.device, viewFormat, {
+ sample: 1,
+ render: sampleCount,
+ });
+
+ // Execute a render pass to sample |sampleSource| into |texture| viewed as |viewFormat|.
+ const commandEncoder = t.device.createCommandEncoder();
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: reinterpretedRenderView,
+ resolveTarget: reinterpretedResolveView,
+ loadOp: 'load',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: sampleSource.createView(),
+ },
+ ],
+ })
+ );
+ pass.draw(6);
+ pass.end();
+
+ // If the render target is multisampled, we'll manually resolve it to check
+ // the contents.
+ const singleSampleRenderTexture = resolveTexture
+ ? t.trackForCleanup(
+ t.device.createTexture({
+ format,
+ size: [kTextureSize, kTextureSize],
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ })
+ )
+ : renderTexture;
+
+ if (resolveTexture) {
+ // Create a pipeline to blit the multisampled render texture to a non-multisample texture.
+ // We are basically performing a manual resolve step to the same format as the original
+ // render texture to check its contents.
+ const pipeline = makeBlitPipeline(t.device, format, { sample: sampleCount, render: 1 });
+ const pass = commandEncoder.beginRenderPass({
+ colorAttachments: [
+ {
+ view: singleSampleRenderTexture.createView(),
+ loadOp: 'load',
+ storeOp: 'store',
+ },
+ ],
+ });
+ pass.setPipeline(pipeline);
+ pass.setBindGroup(
+ 0,
+ t.device.createBindGroup({
+ layout: pipeline.getBindGroupLayout(0),
+ entries: [
+ {
+ binding: 0,
+ resource: renderTexture.createView(),
+ },
+ ],
+ })
+ );
+ pass.draw(6);
+ pass.end();
+ }
+
+ // Submit the commands.
+ t.device.queue.submit([commandEncoder.finish()]);
+
+ // Check the rendered contents.
+ const renderViewTexels = TexelView.fromTexelsAsColors(viewFormat, inputTexelView.color, {
+ clampToFormatRange: true,
+ });
+ t.expectOK(
+ await textureContentIsOKByT2B(
+ t,
+ { texture: singleSampleRenderTexture },
+ [kTextureSize, kTextureSize],
+ { expTexelView: renderViewTexels },
+ { maxDiffULPsForNormFormat: 2 }
+ )
+ );
+
+ // Check the resolved contents.
+ if (resolveTexture) {
+ const resolveView = TexelView.fromTexelsAsColors(viewFormat, renderViewTexels.color, {
+ clampToFormatRange: true,
+ });
+
+ const result = await textureContentIsOKByT2B(
+ t,
+ { texture: resolveTexture },
+ [kTextureSize, kTextureSize],
+ { expTexelView: resolveView },
+ { maxDiffULPsForNormFormat: 2 }
+ );
+ t.expectOK(result);
+ }
+ });