blob: 1a1d127a9a6eae81385ab0bc05b55b915e5263fd [file] [log] [blame]
import { assert, unreachable } from '../../../common/util/util.js';
import { getBlockInfoForTextureFormat } from '../../format_info.js';
import { align } from '../../util/math.js';
import { reifyExtent3D } from '../../util/unions.js';
export type SampleCoord = Required<GPUOrigin3DDict> & { sampleIndex?: number };
/**
* Compute the maximum mip level count allowed for a given texture size and texture dimension.
*/
export function maxMipLevelCount({
size,
dimension = '2d',
}: {
readonly size: Readonly<GPUExtent3DDict> | readonly number[];
readonly dimension?: GPUTextureDimension;
}): number {
const sizeDict = reifyExtent3D(size);
let maxMippedDimension = 0;
switch (dimension) {
case '1d':
maxMippedDimension = 1; // No mipmaps allowed.
break;
case '2d':
maxMippedDimension = Math.max(sizeDict.width, sizeDict.height);
break;
case '3d':
maxMippedDimension = Math.max(sizeDict.width, sizeDict.height, sizeDict.depthOrArrayLayers);
break;
}
return Math.floor(Math.log2(maxMippedDimension)) + 1;
}
/**
* Compute the "physical size" of a mip level: the size of the level, rounded up to a
* multiple of the texel block size.
*/
export function physicalMipSize(
baseSize: Required<GPUExtent3DDict>,
format: GPUTextureFormat,
dimension: GPUTextureDimension,
level: number
): Required<GPUExtent3DDict> {
switch (dimension) {
case '1d':
assert(level === 0, '1d textures cannot be mipmapped');
assert(baseSize.height === 1 && baseSize.depthOrArrayLayers === 1, '1d texture not Wx1x1');
return { width: baseSize.width, height: 1, depthOrArrayLayers: 1 };
case '2d': {
assert(
Math.max(baseSize.width, baseSize.height) >> level > 0,
() => `level (${level}) too large for base size (${baseSize.width}x${baseSize.height})`
);
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);
const virtualWidthAtLevel = Math.max(baseSize.width >> level, 1);
const virtualHeightAtLevel = Math.max(baseSize.height >> level, 1);
const physicalWidthAtLevel = align(virtualWidthAtLevel, blockWidth);
const physicalHeightAtLevel = align(virtualHeightAtLevel, blockHeight);
return {
width: physicalWidthAtLevel,
height: physicalHeightAtLevel,
depthOrArrayLayers: baseSize.depthOrArrayLayers,
};
}
case '3d': {
assert(
Math.max(baseSize.width, baseSize.height, baseSize.depthOrArrayLayers) >> level > 0,
() =>
`level (${level}) too large for base size (${baseSize.width}x${baseSize.height}x${baseSize.depthOrArrayLayers})`
);
const virtualWidthAtLevel = Math.max(baseSize.width >> level, 1);
const virtualHeightAtLevel = Math.max(baseSize.height >> level, 1);
const { blockWidth, blockHeight } = getBlockInfoForTextureFormat(format);
const physicalWidthAtLevel = align(virtualWidthAtLevel, blockWidth);
const physicalHeightAtLevel = align(virtualHeightAtLevel, blockHeight);
return {
width: physicalWidthAtLevel,
height: physicalHeightAtLevel,
depthOrArrayLayers: Math.max(baseSize.depthOrArrayLayers >> level, 1),
};
}
}
}
/**
* Compute the "physical size" of a mip level: the size of the level, rounded up to a
* multiple of the texel block size.
*/
export function physicalMipSizeFromTexture(
texture: GPUTexture,
mipLevel: number
): [number, number, number] {
const size = physicalMipSize(texture, texture.format, texture.dimension, mipLevel);
return [size.width, size.height, size.depthOrArrayLayers];
}
/**
* Compute the "virtual size" of a mip level of a texture (not accounting for texel block rounding).
*
* MAINTENANCE_TODO: Change output to Required<GPUExtent3DDict> for consistency.
*/
export function virtualMipSize(
dimension: GPUTextureDimension,
size: GPUExtent3D,
mipLevel: number
): [number, number, number] {
const { width, height, depthOrArrayLayers } = reifyExtent3D(size);
const shiftMinOne = (n: number) => Math.max(1, n >> mipLevel);
switch (dimension) {
case '1d':
return [shiftMinOne(width), height, depthOrArrayLayers];
case '2d':
return [shiftMinOne(width), shiftMinOne(height), depthOrArrayLayers];
case '3d':
return [shiftMinOne(width), shiftMinOne(height), shiftMinOne(depthOrArrayLayers)];
default:
unreachable();
}
}
/**
* Get texture dimension from view dimension in order to create an compatible texture for a given
* view dimension.
*/
export function getTextureDimensionFromView(viewDimension: GPUTextureViewDimension) {
switch (viewDimension) {
case '1d':
return '1d';
case '2d':
case '2d-array':
case 'cube':
case 'cube-array':
return '2d';
case '3d':
return '3d';
default:
unreachable();
}
}
/** Returns the possible valid view dimensions for a given texture dimension. */
export function viewDimensionsForTextureDimension(textureDimension: GPUTextureDimension) {
switch (textureDimension) {
case '1d':
return ['1d'] as const;
case '2d':
return ['2d', '2d-array', 'cube', 'cube-array'] as const;
case '3d':
return ['3d'] as const;
}
}
/** Returns the effective view dimension for a given texture dimension and depthOrArrayLayers */
export function effectiveViewDimensionForDimension(
viewDimension: GPUTextureViewDimension | undefined,
dimension: GPUTextureDimension | undefined,
depthOrArrayLayers: number
) {
if (viewDimension) {
return viewDimension;
}
switch (dimension || '2d') {
case '1d':
return '1d';
case '2d':
case undefined:
return depthOrArrayLayers > 1 ? '2d-array' : '2d';
break;
case '3d':
return '3d';
default:
unreachable();
}
}
/** Returns the effective view dimension for a given texture */
export function effectiveViewDimensionForTexture(
texture: GPUTexture,
viewDimension: GPUTextureViewDimension | undefined
) {
return effectiveViewDimensionForDimension(
viewDimension,
texture.dimension,
texture.depthOrArrayLayers
);
}
/** Returns the default view dimension for a given texture descriptor. */
export function defaultViewDimensionsForTexture(textureDescriptor: Readonly<GPUTextureDescriptor>) {
const sizeDict = reifyExtent3D(textureDescriptor.size);
return effectiveViewDimensionForDimension(
undefined,
textureDescriptor.dimension,
sizeDict.depthOrArrayLayers
);
}
/** Reifies the optional fields of `GPUTextureDescriptor`.
* MAINTENANCE_TODO: viewFormats should not be omitted here, but it seems likely that the
* @webgpu/types definition will have to change before we can include it again.
*/
export function reifyTextureDescriptor(
desc: Readonly<GPUTextureDescriptor>
): GPUTextureDescriptor &
Required<Omit<GPUTextureDescriptor, 'label' | 'viewFormats' | 'textureBindingViewDimension'>> {
return { dimension: '2d' as const, mipLevelCount: 1, sampleCount: 1, ...desc };
}
/** Reifies the optional fields of `GPUTextureViewDescriptor` (given a `GPUTextureDescriptor`). */
export function reifyTextureViewDescriptor(
textureDescriptor: Readonly<GPUTextureDescriptor>,
view: Readonly<GPUTextureViewDescriptor>
): Required<Omit<GPUTextureViewDescriptor, 'label'>> {
const texture = reifyTextureDescriptor(textureDescriptor);
// IDL defaulting
const baseMipLevel = view.baseMipLevel ?? 0;
const baseArrayLayer = view.baseArrayLayer ?? 0;
const aspect = view.aspect ?? 'all';
const swizzle = view.swizzle ?? 'rgba';
// Spec defaulting
const format = view.format ?? texture.format;
const mipLevelCount = view.mipLevelCount ?? texture.mipLevelCount - baseMipLevel;
const dimension = view.dimension ?? defaultViewDimensionsForTexture(texture);
const usage = (view.usage ?? 0) === 0 ? texture.usage : view.usage!;
let arrayLayerCount = view.arrayLayerCount;
if (arrayLayerCount === undefined) {
if (dimension === '2d-array' || dimension === 'cube-array') {
arrayLayerCount = reifyExtent3D(texture.size).depthOrArrayLayers - baseArrayLayer;
} else if (dimension === 'cube') {
arrayLayerCount = 6;
} else {
arrayLayerCount = 1;
}
}
return {
format,
dimension,
aspect,
usage,
baseMipLevel,
mipLevelCount,
baseArrayLayer,
arrayLayerCount,
swizzle,
};
}
/**
* Get generator of all the coordinates in a subrect.
* @param subrectOrigin - Subrect origin
* @param subrectSize - Subrect size
*/
export function* fullSubrectCoordinates(
subrectOrigin: SampleCoord,
subrectSize: Required<GPUExtent3DDict>,
sampleCount = 1
): Generator<Required<SampleCoord>> {
for (let z = subrectOrigin.z; z < subrectOrigin.z + subrectSize.depthOrArrayLayers; ++z) {
for (let y = subrectOrigin.y; y < subrectOrigin.y + subrectSize.height; ++y) {
for (let x = subrectOrigin.x; x < subrectOrigin.x + subrectSize.width; ++x) {
for (let sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) {
yield { x, y, z, sampleIndex };
}
}
}
}
}