blob: e6ee87f797d729a89addf570ff71c5dcd632715a [file] [log] [blame] [edit]
interface Resource {
readonly buffer?: GPUBufferBindingLayout;
readonly sampler?: GPUSamplerBindingLayout;
readonly texture?: GPUTextureBindingLayout;
readonly storageTexture?: GPUStorageTextureBindingLayout;
readonly externalTexture?: GPUExternalTextureBindingLayout;
readonly code: string;
readonly staticUse?: string;
}
/**
* Returns an array of possible resources
*/
function generateResources(): Resource[] {
const resources: Resource[] = [
// Buffers
{
buffer: { type: 'uniform' },
code: `var<uniform> res : array<vec4u, 16>`,
staticUse: `res[0]`,
},
{
buffer: { type: 'storage' },
code: `var<storage, read_write> res : array<vec4u>`,
staticUse: `res[0]`,
},
{
buffer: { type: 'read-only-storage' },
code: `var<storage> res : array<vec4u>`,
staticUse: `res[0]`,
},
// Samplers
{
sampler: { type: 'filtering' },
code: `var res : sampler`,
},
{
sampler: { type: 'non-filtering' },
code: `var res : sampler`,
},
{
sampler: { type: 'comparison' },
code: `var res : sampler_comparison`,
},
// Multisampled textures
{
texture: { sampleType: 'depth', viewDimension: '2d', multisampled: true },
code: `var res : texture_depth_multisampled_2d`,
},
{
texture: { sampleType: 'unfilterable-float', viewDimension: '2d', multisampled: true },
code: `var res : texture_multisampled_2d<f32>`,
},
{
texture: { sampleType: 'sint', viewDimension: '2d', multisampled: true },
code: `var res : texture_multisampled_2d<i32>`,
},
{
texture: { sampleType: 'uint', viewDimension: '2d', multisampled: true },
code: `var res : texture_multisampled_2d<u32>`,
},
];
// Sampled textures
const sampleDims: GPUTextureViewDimension[] = [
'1d',
'2d',
'2d-array',
'3d',
'cube',
'cube-array',
];
const sampleTypes: GPUTextureSampleType[] = ['float', 'unfilterable-float', 'sint', 'uint'];
const sampleWGSL = ['f32', 'f32', 'i32', 'u32'];
for (const dim of sampleDims) {
let i = 0;
for (const type of sampleTypes) {
resources.push({
texture: { sampleType: type, viewDimension: dim, multisampled: false },
code: `var res : texture_${dim.replace('-', '_')}<${sampleWGSL[i++]}>`,
});
}
}
// Depth textures
const depthDims: GPUTextureViewDimension[] = ['2d', '2d-array', 'cube', 'cube-array'];
for (const dim of depthDims) {
resources.push({
texture: { sampleType: 'depth', viewDimension: dim, multisampled: false },
code: `var res : texture_depth_${dim.replace('-', '_')}`,
});
}
// Storage textures
// Only cover r32uint, r32sint, and r32float here for ease of testing.
const storageDims: GPUTextureViewDimension[] = ['1d', '2d', '2d-array', '3d'];
const formats: GPUTextureFormat[] = ['r32float', 'r32sint', 'r32uint'];
const accesses: GPUStorageTextureAccess[] = ['write-only', 'read-only', 'read-write'];
for (const dim of storageDims) {
for (const format of formats) {
for (const access of accesses) {
resources.push({
storageTexture: { access, format, viewDimension: dim },
code: `var res : texture_storage_${dim.replace('-', '_')}<${format},${access
.replace('-only', '')
.replace('-', '_')}>`,
});
}
}
}
return resources;
}
/**
* Returns a string suitable as a Record key.
*/
function resourceKey(res: Resource): string {
if (res.buffer) {
return `${res.buffer.type}_buffer`;
}
if (res.sampler) {
return `${res.sampler.type}_sampler`;
}
if (res.texture) {
return `texture_${res.texture.sampleType}_${res.texture.viewDimension}_${res.texture.multisampled}`;
}
if (res.storageTexture) {
return `storage_texture_${res.storageTexture.viewDimension}_${res.storageTexture.format}_${res.storageTexture.access}`;
}
if (res.externalTexture) {
return `external_texture`;
}
return ``;
}
/**
* Resource array converted to a Record for nicer test parameterization names.
*/
export const kAPIResources: Record<string, Resource> = Object.fromEntries(
generateResources().map(x => [resourceKey(x), x])
) as Record<string, Resource>;
/**
* Generates a shader of the specified stage using the specified resource at binding (0,0).
*/
export function getWGSLShaderForResource(stage: string, resource: Resource): string {
let code = `@group(0) @binding(0) ${resource.code};\n`;
code += `@${stage}`;
if (stage === 'compute') {
code += `@workgroup_size(1)`;
}
let retTy = '';
let retVal = '';
if (stage === 'vertex') {
retTy = ' -> @builtin(position) vec4f';
retVal = 'return vec4f();';
} else if (stage === 'fragment') {
retTy = ' -> @location(0) vec4f';
retVal = 'return vec4f();';
}
code += `
fn main() ${retTy} {
_ = ${resource.staticUse ?? 'res'};
${retVal}
}
`;
return code;
}
/**
* Generates a bind group layout for for the given resource at binding 0.
*/
export function getAPIBindGroupLayoutForResource(
device: GPUDevice,
stage: GPUShaderStageFlags,
resource: Resource
): GPUBindGroupLayout {
const entry: GPUBindGroupLayoutEntry = {
binding: 0,
visibility: stage,
};
if (resource.buffer) {
entry.buffer = resource.buffer;
}
if (resource.sampler) {
entry.sampler = resource.sampler;
}
if (resource.texture) {
entry.texture = resource.texture;
}
if (resource.storageTexture) {
entry.storageTexture = resource.storageTexture;
}
if (resource.externalTexture) {
entry.externalTexture = resource.externalTexture;
}
const entries: GPUBindGroupLayoutEntry[] = [entry];
return device.createBindGroupLayout({ entries });
}
/**
* Returns true if the sample types are compatible.
*/
function doSampleTypesMatch(api: GPUTextureSampleType, wgsl: GPUTextureSampleType): boolean {
if (api === 'float' || api === 'unfilterable-float') {
return wgsl === 'float' || wgsl === 'unfilterable-float';
}
return api === wgsl;
}
/**
* Returns true if the access modes are compatible.
*/
function doAccessesMatch(api: GPUStorageTextureAccess, wgsl: GPUStorageTextureAccess): boolean {
if (api === 'read-write') {
return wgsl === 'read-write' || wgsl === 'write-only';
}
return api === wgsl;
}
/**
* Returns true if the resources are compatible.
*/
export function doResourcesMatch(api: Resource, wgsl: Resource): boolean {
if (api.buffer) {
if (!wgsl.buffer) {
return false;
}
return api.buffer.type === wgsl.buffer.type;
}
if (api.sampler) {
if (!wgsl.sampler) {
return false;
}
return (
api.sampler.type === wgsl.sampler.type ||
(api.sampler.type !== 'comparison' && wgsl.sampler.type !== 'comparison')
);
}
if (api.texture) {
if (!wgsl.texture) {
return false;
}
const aType = api.texture.sampleType as GPUTextureSampleType;
const wType = wgsl.texture.sampleType as GPUTextureSampleType;
return (
doSampleTypesMatch(aType, wType) &&
api.texture.viewDimension === wgsl.texture.viewDimension &&
api.texture.multisampled === wgsl.texture.multisampled
);
}
if (api.storageTexture) {
if (!wgsl.storageTexture) {
return false;
}
const aAccess = api.storageTexture.access as GPUStorageTextureAccess;
const wAccess = wgsl.storageTexture.access as GPUStorageTextureAccess;
return (
doAccessesMatch(aAccess, wAccess) &&
api.storageTexture.format === wgsl.storageTexture.format &&
api.storageTexture.viewDimension === wgsl.storageTexture.viewDimension
);
}
if (api.externalTexture) {
return wgsl.externalTexture !== undefined;
}
return false;
}