webgpu-spd
v2.0.1
Published
A port of AMD's Single Pass Downsampler for WebGPU
Downloads
10
Maintainers
Readme
WebGPU SPD
A utility library for generating up to 12 mip levels for 2d textures & texture arrays in a single WebGPU compute pass.
Docs
Find the docs here.
Try it out here.
Installation
NPM
npm install webgpu-spd
From GitHub
import { WebGPUSinglePassDownsampler } from 'https://jolifantobambla.github.io/webgpu-spd/2.x/dist/index.js';
From UNPKG
import { WebGPUSinglePassDownsampler } from 'https://unpkg.com/[email protected]/dist/index.js';
Usage
WebGPU SPD downsamples 2d textures and 2d texture arrays using compute pipelines generating up to 12 mip levels in a single pass (all array layers are processed in the same pass). The maximum number of mip levels that can be generated within a single pass depends on the maxStorageTexturesPerShaderStage
limit supported by the device used.
Should the number of mip levels requested for a texture exceed this limit, multiple passes, generating up to min(maxStorageTexturesPerShaderStage, 12)
mip levels each, will be used instead.
The mip levels generated for a given input texture are stored either in the input texture or in a separate target texture if specified.
This output texture must support GPUTextureUsage.STORAGE_BINDING
with access mode "write-only"
.
Generate mipmaps
import { WebGPUSinglePassDownsampler, maxMipLevelCount } from 'webgpu-spd';
const downsampler = new WebGPUSinglePassDownsampler();
const size = [/* size + array layers */];
const texture = device.createTexture({
size,
mipLevelCount: maxMipLevelCount(size[0], size[1]),
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
});
// write mip level 0
downsampler.generateMipmaps(device, texture);
Downsample a texture each frame
import { WebGPUSinglePassDownsampler, SPDFilters } from 'webgpu-spd';
// during setup
const downsampler = new WebGPUSinglePassDownsampler();
const downsampleDepthPass = downsampler.preparePass(device, linearDepthTexture, { filter: SPDFilters.Min });
// in render loop
const commandEncoder = device.createCommandEncoder();
const computePassEncoder = commandEncoder.beginComputePass();
downsampleDepthPass.encode(computePassEncoder);
computePassEncoder.end();
device.queue.submit([commandEncoder.finish()]);
Downsample into target
import { WebGPUSinglePassDownsampler, maxMipLevelCount } from 'webgpu-spd';
const downsampler = new WebGPUSinglePassDownsampler();
const size = [/* width, height, array layers */];
const texture = device.createTexture({
size,
mipLevelCount: 1,
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING,
});
const target = device.createTexture({
size: [size[0] / 2, size[1] / 2, size[2]],
mipLevelCount: maxMipLevelCount(size[0], size[1]) - 1,
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
});
// write mip level 0
downsampler.generateMipmaps(device, texture, { target });
Use min-max filter to generate a min-max pyramid for single-channel textures
The SPDFilters.MinMax
filter provided by WebGPU SPD is a special filter that is meant to be used with input textures using single-channel formats like "r32float"
, and a target texture using a two-channel format like "rg32float"
.
After the downsampling pass, the target texture will contain the minimum values in the red channel and the maximum values in the green channel.
import { WebGPUSinglePassDownsampler, SPDFilters, maxMipLevelCount } from 'webgpu-spd';
// during setup
const downsampler = new WebGPUSinglePassDownsampler();
const linearDepth = device.createTexture({
size: [/* gBuffer size */],
mipLevelCount: 1,
format: 'r32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
});
const minMaxDepthPyramid = device.createTexture({
size: [linearDepth.width / 2, linearDepth.height / 2],
mipLevelCount: maxMipLevelCount(linearDepth.width, linearDepth.height) - 1
format: 'rg32float',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING,
});
const minMaxDepthPass = downsampler.preparePass(device, linearDepth, {
target: minMaxDepthPyramid,
filter: SPDFilters.MinMax,
});
// in render loop
// ... write mip level 0 of linearDepth
minMaxDepthPass.encode(computePassEncoder);
Prepare pipelines for expected formats
In the above examples, GPU resources, like compute pipelines and bind group layouts etc., are created on the fly the first time a new configuration of GPUDevice
, GPUTextureFormat
, filter, and precision is needed.
WebGPU SPD also supports allocating resources during setup, like this:
import { WebGPUSinglePassDownsampler, SPDFilters, SPDPrecision } from 'webgpu-spd';
const downsampler = new WebGPUSinglePassDownsampler({ device, formats: [
{ format: 'rgba8unorm', halfPrecision: true },
{ format: 'r32float', filters: [ SPDFilters.Min ] },
]});
// alternatively call
downsampler.prepareDeviceResources({ device, formats: [
{ format: 'rgba8unorm', halfPrecision: true },
{ format: 'r32float', filters: [ SPDFilters.Min ] },
]});
Limit the number of mip levels and array layers per pass
Generating more than 6 mip levels per pass might not be supported on each platform due to buffers being not coherent by default yet.
WebGPU SPD uses min(device.limits.maxStorageTexturesPerPass, 12)
by default and can thus be implicitly configured using the device's limit.
However, this might not be desirable in all cases, so WebGPU SPD can be configured to use a different limit by setting the corresponding option when preparing device resources.
If more than 6 mip levels are downsampled per pass, WebGPU SPD allocates additional internal resources to store intermediate texture data (16 * 64 * 64 * maxArrayLayersPerPass
bytes) and for control flow purposes (4 * maxArrayLayersPerPass
bytes).
The size of these resources depends on the number of array layers that can be downsampled each pass.
If a texture's number of array layers exceeds the number of array layers per pass, multiple passes will be used instead.
By default, WebGPU SPD uses the device's maxTextureArrayLayers
limit.
WebGPU SPD can be configured to use different limits like this:
import { WebGPUSinglePassDownsampler, SPDFilters } from 'webgpu-spd';
const downsampler = new WebGPUSinglePassDownsampler({ device, maxMipsPerPass: 6, maxArrayLayersPerPass: 1 });
// alternatively call
downsampler.prepareDeviceResources({ device, maxMipsPerPass: 6, maxArrayLayersPerPass: 1 });
Handling device loss
import { WebGPUSinglePassDownsampler, SPDFilters } from 'webgpu-spd';
const formatConfigs = [
{ format: 'rgba8unorm' },
{ format: 'r32float', filters: [ SPDFilters.Min ] },
];
// on new device
downsampler.deregisterDevice(oldDevice);
downsampler.prepareDeviceResources({ device: newDevice, formats: formatConfig s});
downsampleTexturePass = downsampler.preparePass(newDevice, texture);
Use custom filters
Custom filters for downsampling a quad to a single pixel can be registered with WebGPU SPD using registerFilter
.
The given WGSL code must at least define a reduction function with the following name and signature:
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar>
If a filter is known to be only used with a single scalar type (e.g., u32
), uses of SPDScalar
can also be replaced by that scalar type.
For example, a custom filter that only takes a single pixel value out of the four given ones could be implemented and used like this:
import { WebGPUSinglePassDownsampler } from 'webgpu-spd';
const downsampler = new WebGPUSinglePassDownsampler();
downsampler.registerFilter('upperLeft', `
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
return v0;
}
`);
// ...
downsampler.generateMipmaps(device, texture, { filter: 'upperLeft' });
Downsample image region
import { WebGPUSinglePassDownsampler } from 'webgpu-spd';
const downsampler = new WebGPUSinglePassDownsampler();
const sizeHalf = [texture.width / 2, texture.height / 2];
downsampler.generateMipmaps(device, texture, { offset: sizeHalf, size: sizeHalf});
Contributions
Contributions are very welcome. If you find a bug or think some important functionality is missing, please file an issue here. If want to help out yourself, feel free to submit a pull request here.
Acknowledgements
This library is a WebGPU port of the FidelityFX Single Pass Downsampler (SPD) included in AMD's FidelityFX-SDK.