From 70ab87a599bd67b0a97f7c09771f12098befdbcf Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Mon, 11 Nov 2024 16:39:21 -0800 Subject: [PATCH] Print out bias info for various texture sizes --- .../call/builtin/textureSampleBias.spec.ts | 226 ++++++++++++++++++ .../expression/call/builtin/texture_utils.ts | 3 +- 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts b/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts index 209602508550..811dad72dcec 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts @@ -10,6 +10,7 @@ Samples a texture with a bias to the mip level. import { makeTestGroup } from '../../../../../../common/framework/test_group.js'; import { isFilterableAsTextureF32, kAllTextureFormats } from '../../../../../format_info.js'; import { TextureTestMixin } from '../../../../../gpu_test.js'; +import { clamp } from '../../../../../util/math.js'; import { vec2, @@ -37,6 +38,230 @@ import { export const g = makeTestGroup(TextureTestMixin(WGSLTextureSampleTest)); +function makeGraph(width: number, height: number) { + const data = new Uint8Array(width * height); + + return { + plot(norm: number, x: number, c: number) { + const y = clamp(Math.floor(norm * height), { min: 0, max: height - 1 }); + const offset = (height - y - 1) * width + x; + data[offset] = c; + }, + plotValues(values: Iterable, c: number) { + let i = 0; + for (const v of values) { + this.plot(v, i, c); + ++i; + } + }, + toString(conversion = ['.', 'e', 'A']) { + const lines = []; + for (let y = 0; y < height; ++y) { + const offset = y * width; + lines.push([...data.subarray(offset, offset + width)].map(v => conversion[v]).join('')); + } + return lines.join('\n'); + }, + }; +} + +function safeStr(v?: string | number) { + return v === undefined ? 'undefined' : v.toString(); +} + +function pad(format: string, len: number, v: string | number) { + switch (format) { + case '>': // move to right + case 'r': // pad right + case 's': // pad start + return safeStr(v).padStart(len); + default: + return safeStr(v).padEnd(len); + } +} + +function padColumns(rows: (string | number)[][], formats = '') { + const columnLengths: number[] = []; + + // get size of each column + for (const row of rows) { + row.forEach((v, i) => { + columnLengths[i] = Math.max(columnLengths[i] || 0, safeStr(v).length); + }); + } + + return rows + .map(row => row.map((v, i) => pad(formats[i], columnLengths[i], v)).join('')) + .join('\n'); +} + +g.test('info') + .desc( + ` + test various bias settings for a given mip level with different texture sizes +` + ) + .fn(async t => { + const { device } = t; + const biases = [ + -16, -15.9, -15.8, -15, 8, 9, 10, 11, 12, 12.125, 12.25, 12.5, 12.75, 13, 14, 15, 15.99, + ]; + + const module = device.createShaderModule({ + code: ` +struct VOut { + @builtin(position) pos: vec4f, + @location(0) @interpolate(flat, either) ndx: u32, + @location(1) @interpolate(flat, either) result: vec4, +}; + +struct Data { + derivativeMult: f32, + bias: f32, + pad0: f32, + pad1: f32, +}; + +@group(0) @binding(0) var T : texture_2d; +@group(0) @binding(1) var S : sampler; +@group(0) @binding(2) var data : array; + +fn getResult(idx: u32, derivativeBase: vec2f) -> vec4 { + let args = data[idx]; + return textureSampleBias(T, S, vec2f(0.5) + derivativeBase * vec2f(args.derivativeMult, 0), args.bias); +} + +// --------------------------- fragment stage shaders -------------------------------- +@vertex fn vsFragment( + @builtin(vertex_index) vertex_index : u32, + @builtin(instance_index) instance_index : u32) -> VOut { + let positions = array(vec2f(-1, 3), vec2f(3, -1), vec2f(-1, -1)); + return VOut(vec4f(positions[vertex_index], 0, 1), instance_index, vec4(0)); +} + +@fragment fn fsFragment(v: VOut) -> @location(0) vec4u { + let derivativeBase = (v.pos.xy - 0.5 - vec2f(f32(v.ndx), 0)) / vec2f(textureDimensions(T)); + return bitcast(getResult(v.ndx, derivativeBase)); + //return bitcast(vec4f(data[v.ndx].bias)); +} + `, + }); + + const pipeline = device.createRenderPipeline({ + layout: 'auto', + vertex: { module }, + fragment: { module, targets: [{ format: 'rgba32uint' }] }, + }); + + const sampler = device.createSampler({ + minFilter: 'linear', + magFilter: 'linear', + mipmapFilter: 'linear', + }); + + const data = new Float32Array(biases.length * 4); + biases.forEach((bias, i) => { + const mipLevel = 0.5; + const derivativeBasedMipLevel = mipLevel - bias; + const derivativeMult = Math.pow(2, derivativeBasedMipLevel); + const offset = i * 4; + data[offset + 0] = derivativeMult; + data[offset + 1] = bias; + }); + + const dataBuffer = t.createBufferTracked({ + size: data.byteLength, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + device.queue.writeBuffer(dataBuffer, 0, data); + + const sizes = [2, 4, 8, 12, 16, 24, 32, 40, 128]; + const buffers: GPUBuffer[] = []; + await Promise.all( + sizes.map(size => { + const texture = t.createTextureTracked({ + size: [size, size], + format: 'r8unorm', + usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING, + mipLevelCount: 2, + }); + + // fill mip level 1 with ones + const ones = new Uint8Array((texture.width / 2) * (texture.height / 2)).fill(255); + device.queue.writeTexture({ texture, mipLevel: 1 }, ones, { bytesPerRow: size / 2 }, [ + size / 2, + size / 2, + ]); + + const bindGroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: texture.createView() }, + { binding: 1, resource: sampler }, + { binding: 2, resource: { buffer: dataBuffer } }, + ], + }); + + const resultTexture = t.createTextureTracked({ + size: [biases.length, 1], + format: 'rgba32uint', + usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + }); + + const resultBuffer = t.createBufferTracked({ + size: resultTexture.width * 4 * 4, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ, + }); + + const encoder = device.createCommandEncoder(); + const pass = encoder.beginRenderPass({ + colorAttachments: [ + { + view: resultTexture.createView(), + loadOp: 'clear', + storeOp: 'store', + }, + ], + }); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + for (let i = 0; i < biases.length; ++i) { + pass.setViewport(i, 0, 1, 1, 0, 1); + pass.draw(3, 1, 0, i); + } + pass.end(); + encoder.copyTextureToBuffer( + { texture: resultTexture }, + { + buffer: resultBuffer, + }, + [biases.length, 1] + ); + device.queue.submit([encoder.finish()]); + buffers.push(resultBuffer); + return resultBuffer.mapAsync(GPUMapMode.READ); + }) + ); + + const graph = makeGraph(biases.length, 20); + + const rows: (number | string)[][] = [['bias->', ...biases.map(v => `|${v}`)]]; + rows.push(rows[0].map(v => '|-------')); + sizes.forEach((size, i) => { + const results = new Float32Array(buffers[i].getMappedRange()); + const row: (number | string)[] = [`size:${size}`]; + for (let j = 0; j < results.length; j += 4) { + graph.plot((1 - results[j] - 0.4) / 0.2, j / 4, i + 1); + row.push(`|${(1 - results[j]).toFixed(5)}`); + } + rows.push(row); + }); + + t.info(`\n${padColumns(rows)}`); + t.info(`\n${graph.toString(['.', ...biases.map((v, i) => String.fromCodePoint(97 + i))])}`); + t.expectOK(new Error('info')); + }); + g.test('sampled_2d_coords') .specURL('https://www.w3.org/TR/WGSL/#texturesamplebias') .desc( @@ -111,6 +336,7 @@ Parameters: offset, }; }); + const viewDescriptor = {}; const textureType = 'texture_2d'; const results = await doTextureCalls( diff --git a/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts b/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts index 90d2f937126c..ef2c80612d10 100644 --- a/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts +++ b/src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts @@ -2190,7 +2190,7 @@ export async function checkCallResults( maxFractionalDiff ) ) { - continue; + // continue; } if (!sampler && okBecauseOutOfBounds(texture, call, gotRGBA, maxFractionalDiff)) { @@ -2208,6 +2208,7 @@ export async function checkCallResults( : kRComponent; let bad = false; + bad = true; const diffs = rgbaComponentsToCheck.map(component => { const g = gotRGBA[component]!; const e = expectRGBA[component]!;