in src/webgpu/shader/execution/expression/call/builtin/texture_utils.ts [3340:3678]
async function identifySamplePoints<T extends Dimensionality>(
softwareTexture: SoftwareTexture,
sampler: GPUSamplerDescriptor,
callForSamples: TextureCall<T>,
originalCall: TextureCall<T>,
texels: TexelView[] | undefined,
run: (texels: TexelView[]) => Promise<PerTexelComponent<number>>
) {
const info = softwareTexture.descriptor;
const isCube = isCubeViewDimension(softwareTexture.viewDescriptor);
const mipLevelCount = softwareTexture.descriptor.mipLevelCount ?? 1;
const mipLevelSizes = range(mipLevelCount, mipLevel =>
virtualMipSize(
softwareTexture.descriptor.dimension ?? '2d',
softwareTexture.descriptor.size,
mipLevel
)
);
const numTexelsPerLevel = mipLevelSizes.map(size => size.reduce((s, v) => s * v));
const numTexelsOfPrecedingLevels = (() => {
let total = 0;
return numTexelsPerLevel.map(v => {
const num = total;
total += v;
return num;
});
})();
const numTexels = numTexelsPerLevel.reduce((sum, v) => sum + v);
const getMipLevelFromTexelId = (texelId: number) => {
for (let mipLevel = mipLevelCount - 1; mipLevel > 0; --mipLevel) {
if (texelId - numTexelsOfPrecedingLevels[mipLevel] >= 0) {
return mipLevel;
}
}
return 0;
};
const getTexelCoordFromTexelId = (texelId: number) => {
const mipLevel = getMipLevelFromTexelId(texelId);
const size = mipLevelSizes[mipLevel];
const texelsPerSlice = size[0] * size[1];
const id = texelId - numTexelsOfPrecedingLevels[mipLevel];
const layer = Math.floor(id / texelsPerSlice);
const xyId = id - layer * texelsPerSlice;
const y = (xyId / size[0]) | 0;
const x = xyId % size[0];
return { x, y, z: layer, mipLevel, xyId };
};
// This isn't perfect. We already know there was an error. We're just
// generating info so it seems okay it's not perfect. This format will
// be used to generate weights by drawing with a texture of this format
// with a specific pixel set to [1, 1, 1, 1]. As such, if the result
// is > 0 then that pixel was sampled and the results are the weights.
//
// Ideally, this texture with a single pixel set to [1, 1, 1, 1] would
// be the same format we were originally testing, the one we already
// detected an error for. This way, whatever subtle issues there are
// from that format will affect the weight values we're computing. But,
// if that format is not encodable, for example if it's a compressed
// texture format, then we have no way to build a texture so we use
// rgba8unorm instead.
const format = (
kEncodableTextureFormats.includes(info.format as EncodableTextureFormat)
? info.format
: isDepthTextureFormat(info.format)
? 'depth16unorm'
: 'rgba8unorm'
) as EncodableTextureFormat;
const rep = kTexelRepresentationInfo[format];
const components = isBuiltinGather(callForSamples.builtin) ? kRGBAComponents : rep.componentOrder;
const convertResultAsAppropriate = isBuiltinGather(callForSamples.builtin)
? <T>(v: T) => v
: convertResultFormatToTexelViewFormat;
// Identify all the texels that are sampled, and their weights.
const sampledTexelWeights = new Map<number, PerTexelComponent<number>>();
const unclassifiedStack = [new Set<number>(range(numTexels, v => v))];
while (unclassifiedStack.length > 0) {
// Pop the an unclassified texels stack
const unclassified = unclassifiedStack.pop()!;
// Split unclassified texels evenly into two new sets
const setA = new Set<number>();
const setB = new Set<number>();
[...unclassified.keys()].forEach((t, i) => ((i & 1) === 0 ? setA : setB).add(t));
// Push setB to the unclassified texels stack
if (setB.size > 0) {
unclassifiedStack.push(setB);
}
// See if any of the texels in setA were sampled.0
const results = convertResultAsAppropriate(
await run(
range(mipLevelCount, mipLevel =>
TexelView.fromTexelsAsColors(
format,
(coords: Required<GPUOrigin3DDict>): Readonly<PerTexelComponent<number>> => {
const size = mipLevelSizes[mipLevel];
const texelsPerSlice = size[0] * size[1];
const texelsPerRow = size[0];
const texelId =
numTexelsOfPrecedingLevels[mipLevel] +
coords.x +
coords.y * texelsPerRow +
coords.z * texelsPerSlice;
const isCandidate = setA.has(texelId);
const texel: PerTexelComponent<number> = {};
for (const component of rep.componentOrder) {
texel[component] = isCandidate ? 1 : 0;
}
return texel;
}
)
)
),
format
);
if (components.some(c => results[c] !== 0)) {
// One or more texels of setA were sampled.
if (setA.size === 1) {
// We identified a specific texel was sampled.
// As there was only one texel in the set, results holds the sampling weights.
setA.forEach(texel => sampledTexelWeights.set(texel, results));
} else {
// More than one texel in the set. Needs splitting.
unclassifiedStack.push(setA);
}
}
}
// separate the sampledTexelWeights by mipLevel, then by layer, within a layer the texelId only includes x and y
const levels: Map<number, PerTexelComponent<number>>[][] = [];
for (const [texelId, weight] of sampledTexelWeights.entries()) {
const { xyId, z, mipLevel } = getTexelCoordFromTexelId(texelId);
const level = levels[mipLevel] ?? [];
levels[mipLevel] = level;
const layerEntries = level[z] ?? new Map();
level[z] = layerEntries;
layerEntries.set(xyId, weight);
}
// example when blockWidth = 2, blockHeight = 2
//
// 0 1 2 3
// ╔═══╤═══╦═══╤═══╗
// 0 ║ a │ ║ │ ║
// ╟───┼───╫───┼───╢
// 1 ║ │ ║ │ ║
// ╠═══╪═══╬═══╪═══╣
// 2 ║ │ ║ │ ║
// ╟───┼───╫───┼───╢
// 3 ║ │ ║ │ b ║
// ╚═══╧═══╩═══╧═══╝
/* prettier-ignore */
const blockParts = {
top: { left: '╔', fill: '═══', right: '╗', block: '╦', texel: '╤' },
mid: { left: '╠', fill: '═══', right: '╣', block: '╬', texel: '╪' },
bot: { left: '╚', fill: '═══', right: '╝', block: '╩', texel: '╧' },
texelMid: { left: '╟', fill: '───', right: '╢', block: '╫', texel: '┼' },
value: { left: '║', fill: ' ', right: '║', block: '║', texel: '│' },
} as const;
/* prettier-ignore */
const nonBlockParts = {
top: { left: '┌', fill: '───', right: '┐', block: '┬', texel: '┬' },
mid: { left: '├', fill: '───', right: '┤', block: '┼', texel: '┼' },
bot: { left: '└', fill: '───', right: '┘', block: '┴', texel: '┴' },
texelMid: { left: '├', fill: '───', right: '┤', block: '┼', texel: '┼' },
value: { left: '│', fill: ' ', right: '│', block: '│', texel: '│' },
} as const;
const lines: string[] = [];
const letter = (idx: number) => String.fromCodePoint(idx < 30 ? 97 + idx : idx + 9600 - 30); // 97: 'a'
let idCount = 0;
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(
softwareTexture.descriptor.format
);
// range + concatenate results.
const rangeCat = <T>(num: number, fn: (i: number) => T) => range(num, fn).join('');
const joinFn = (arr: string[], fn: (i: number) => string) => {
const joins = range(arr.length - 1, fn);
return arr.map((s, i) => `${s}${joins[i] ?? ''}`).join('');
};
const parts = Math.max(blockWidth, blockHeight) > 1 ? blockParts : nonBlockParts;
/**
* Makes a row that's [left, fill, texel, fill, block, fill, texel, fill, right]
* except if `contents` is supplied then it would be
* [left, contents[0], texel, contents[1], block, contents[2], texel, contents[3], right]
*/
const makeRow = (
blockPaddedWidth: number,
width: number,
{
left,
fill,
right,
block,
texel,
}: {
left: string;
fill: string;
right: string;
block: string;
texel: string;
},
contents?: string[]
) => {
return `${left}${joinFn(contents ?? range(blockPaddedWidth, x => fill), x => {
return (x + 1) % blockWidth === 0 ? block : texel;
})}${right}`;
};
for (let mipLevel = 0; mipLevel < mipLevelCount; ++mipLevel) {
const level = levels[mipLevel];
if (!level) {
continue;
}
const [width, height, depthOrArrayLayers] = mipLevelSizes[mipLevel];
const texelsPerRow = width;
for (let layer = 0; layer < depthOrArrayLayers; ++layer) {
const layerEntries = level[layer];
const orderedTexelIndices: number[] = [];
lines.push('');
const unSampled = layerEntries ? '' : 'un-sampled';
if (isCube) {
const face = kFaceNames[layer % 6];
lines.push(
`layer: ${layer} mip(${mipLevel}), cube-layer: ${(layer / 6) | 0} (${face}) ${unSampled}`
);
} else {
lines.push(`layer: ${layer} mip(${mipLevel}) ${unSampled}`);
}
if (!layerEntries) {
continue;
}
const blockPaddedHeight = align(height, blockHeight);
const blockPaddedWidth = align(width, blockWidth);
lines.push(` ${rangeCat(width, x => ` ${x.toString().padEnd(2)}`)}`);
lines.push(` ${makeRow(blockPaddedWidth, width, parts.top)}`);
for (let y = 0; y < blockPaddedHeight; y++) {
lines.push(
`${y.toString().padStart(2)} ${makeRow(
blockPaddedWidth,
width,
parts.value,
range(blockPaddedWidth, x => {
const texelIdx = x + y * texelsPerRow;
const weight = layerEntries.get(texelIdx);
const outside = y >= height || x >= width;
if (outside || weight === undefined) {
return outside ? '░░░' : ' ';
} else {
const id = letter(idCount + orderedTexelIndices.length);
orderedTexelIndices.push(texelIdx);
return ` ${id} `;
}
})
)}`
);
// It's either a block row, a texel row, or the last row.
const end = y < blockPaddedHeight - 1;
const lineParts = end
? (y + 1) % blockHeight === 0
? parts.mid
: parts.texelMid
: parts.bot;
lines.push(` ${makeRow(blockPaddedWidth, width, lineParts)}`);
}
const pad2 = (n: number) => n.toString().padStart(2);
const pad3 = (n: number) => n.toString().padStart(3);
const fix5 = (n: number) => {
const s = n.toFixed(5);
return s === '0.00000' && n !== 0 ? n.toString() : s;
};
const formatValue = isSintOrUintFormat(format) ? pad3 : fix5;
const formatTexel = (texel: PerTexelComponent<number> | undefined) =>
texel
? Object.entries(texel)
.map(([k, v]) => `${k}: ${formatValue(v)}`)
.join(', ')
: '*texel values unavailable*';
const colorLines: string[] = [];
const compareLines: string[] = [];
let levelWeight = 0;
orderedTexelIndices.forEach((texelIdx, i) => {
const weights = layerEntries.get(texelIdx)!;
const y = Math.floor(texelIdx / texelsPerRow);
const x = texelIdx % texelsPerRow;
const singleWeight = valueIfAllComponentsAreEqual(weights, components)!;
levelWeight += singleWeight;
const w =
singleWeight !== undefined
? `weight: ${fix5(singleWeight)}`
: `weights: [${components.map(c => `${c}: ${fix5(weights[c]!)}`).join(', ')}]`;
const coord = `${pad2(x)}, ${pad2(y)}, ${pad2(layer)}`;
const texel =
texels &&
convertToTexelViewFormat(
texels[mipLevel].color({ x, y, z: layer }),
softwareTexture.descriptor.format
);
const texelStr = formatTexel(texel);
const id = letter(idCount + i);
lines.push(`${id}: mip(${mipLevel}) at: [${coord}], ${w}`);
colorLines.push(`${id}: value: ${texelStr}`);
if (isBuiltinComparison(originalCall.builtin)) {
assert(!!texel);
const compareTexel = applyCompare(originalCall, sampler, [TexelComponent.Depth], texel);
compareLines.push(
`${id}: compare(${sampler.compare}) result with depthRef(${fix5(
originalCall.depthRef!
)}): ${fix5(compareTexel.Depth!)}`
);
}
});
lines.push(...colorLines);
lines.push(...compareLines);
if (!isNaN(levelWeight)) {
lines.push(`mip level (${mipLevel}) weight: ${fix5(levelWeight)}`);
}
idCount += orderedTexelIndices.length;
}
}
return lines;
}