Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

op: Implement 'filter_mode,minFilter' test in filter_mode.spec.ts #2090

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 167 additions & 1 deletion src/webgpu/api/operation/sampling/filter_mode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,172 @@ TODO:
`;

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { assert } from '../../../../common/util/util.js';
import { GPUTest } from '../../../gpu_test.js';

export const g = makeTestGroup(GPUTest);
const kRTSize = 16;
const xMiddle = kRTSize / 2; // we check the pixel value in the middle of the render target
const kColorAttachmentFormat = 'rgba8unorm';
const colors = [
new Uint8Array([0xff, 0x00, 0x00, 0xff]), // miplevel = 0
new Uint8Array([0x00, 0xff, 0x00, 0xff]), // miplevel = 1
new Uint8Array([0x00, 0x00, 0xff, 0xff]), // miplevel = 2
];

class SamplerFilterModeSlantedPlaneTest extends GPUTest {
private pipeline: GPURenderPipeline | undefined;
async init(): Promise<void> {
await super.init();

this.pipeline = this.device.createRenderPipeline({
layout: 'auto',
vertex: {
module: this.device.createShaderModule({
code: `
struct Outputs {
@builtin(position) Position : vec4<f32>,
@location(0) fragUV : vec2<f32>,
};

@vertex fn main(
@builtin(vertex_index) VertexIndex : u32) -> Outputs {
var position : array<vec3<f32>, 6> = array<vec3<f32>, 6>(
vec3<f32>(-0.5, 0.5, -0.5),
vec3<f32>(0.5, 0.5, -0.5),
vec3<f32>(-0.5, 0.5, 0.5),
vec3<f32>(-0.5, 0.5, 0.5),
vec3<f32>(0.5, 0.5, -0.5),
vec3<f32>(0.5, 0.5, 0.5));
// uv is pre-scaled to mimic repeating tiled texture
var uv : array<vec2<f32>, 6> = array<vec2<f32>, 6>(
vec2<f32>(0.0, 0.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(0.0, 50.0),
vec2<f32>(0.0, 50.0),
vec2<f32>(1.0, 0.0),
vec2<f32>(1.0, 50.0));
// draw a slanted plane in a specific way
let matrix : mat4x4<f32> = mat4x4<f32>(
vec4<f32>(-1.7320507764816284, 1.8322050568049563e-16, -6.176817699518044e-17, -6.170640314703498e-17),
vec4<f32>(-2.1211504944260596e-16, -1.496108889579773, 0.5043753981590271, 0.5038710236549377),
vec4<f32>(0.0, -43.63650894165039, -43.232173919677734, -43.18894577026367),
vec4<f32>(0.0, 21.693578720092773, 21.789791107177734, 21.86800193786621));

var output : Outputs;
output.fragUV = uv[VertexIndex];
output.Position = matrix * vec4<f32>(position[VertexIndex], 1.0);
return output;
}
`,
}),
entryPoint: 'main',
},
fragment: {
module: this.device.createShaderModule({
code: `
@group(0) @binding(0) var sampler0 : sampler;
@group(0) @binding(1) var texture0 : texture_2d<f32>;

@fragment fn main(
@builtin(position) FragCoord : vec4<f32>,
@location(0) fragUV: vec2<f32>)
-> @location(0) vec4<f32> {
return textureSample(texture0, sampler0, fragUV);
}
`,
}),
entryPoint: 'main',
targets: [{ format: 'rgba8unorm' }],
},
primitive: { topology: 'triangle-list' },
});
}

// return the render target texture object
drawSlantedPlane(textureView: GPUTextureView, sampler: GPUSampler): GPUTexture {
// make sure it's already initialized
assert(this.pipeline !== undefined);

const bindGroup = this.device.createBindGroup({
entries: [
{ binding: 0, resource: sampler },
{ binding: 1, resource: textureView },
],
layout: this.pipeline.getBindGroupLayout(0),
});

const colorAttachment = this.device.createTexture({
format: kColorAttachmentFormat,
size: { width: kRTSize, height: kRTSize, depthOrArrayLayers: 1 },
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const colorAttachmentView = colorAttachment.createView();

const encoder = this.device.createCommandEncoder();
const pass = encoder.beginRenderPass({
colorAttachments: [
{
view: colorAttachmentView,
storeOp: 'store',
clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
loadOp: 'clear',
},
],
});
pass.setPipeline(this.pipeline);
pass.setBindGroup(0, bindGroup);
pass.draw(6);
pass.end();
this.device.queue.submit([encoder.finish()]);

return colorAttachment;
}
}

export const g = makeTestGroup(SamplerFilterModeSlantedPlaneTest);

g.test('mipmapFilter')
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I couldn't find a proper way to test the filter modes yet. I aimed to write a test that filter mode affected the expected colors in drawing the slanted plane. But, the filter modes(mipmapFilter first for now) didn't affect the expected result. Could you please give me any idea or comment on how to test the fooFilter affects the expected color?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be useful to visualize the colorAttachment. Unfortunately we never managed to add good facilities for this - I think I started writing some but never finished.

When running in a browser, you can hack in code to visualize it, e.g. add a canvas to the document.body and change drawSlantedPlane to get its attachment from getCurrentTexture instead of createTexture. Example:

Gyuyoung/cts@filter-mode-minFilter...kainino0x:cts:example-visualization
Here you can see there is a slight difference in the result (but it's subtle):

image linear
image nearest

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for giving me more information. I don't find a way to test it yet. Let me take a look at this more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.desc('Test that mipmapFilter affects drawing the slanted plane with different filter modes.')
.params(u =>
u //
.combine('mipmapFilter', ['linear', 'nearest'] as const)
.combineWithParams([
{
results: [
{ coord: { x: xMiddle, y: 2 }, expected: colors[2] },
{ coord: { x: xMiddle, y: 6 }, expected: [colors[0], colors[1]] },
],
},
])
)
.fn(async t => {
const { mipmapFilter } = t.params;

const texture = t.createTexture2DWithMipmaps(colors);
const textureView = texture.createView();

const sampler = t.device.createSampler({
mipmapFilter,
});

const colorAttachment = t.drawSlantedPlane(textureView, sampler);

for (const entry of t.params.results) {
if (entry.expected instanceof Uint8Array) {
// equal exactly one color
t.expectSinglePixelIn2DTexture(colorAttachment, kColorAttachmentFormat, entry.coord, {
exp: entry.expected,
});
} else {
// a lerp between two colors
t.expectSinglePixelBetweenTwoValuesIn2DTexture(
colorAttachment,
kColorAttachmentFormat,
entry.coord,
{
exp: entry.expected as [Uint8Array, Uint8Array],
}
);
}
}
});