typegpu
Version:
A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.
189 lines (188 loc) • 5.96 kB
JavaScript
import { getAttributesString } from "../../data/attributes.js";
import { isLooseData } from "../../data/dataTypes.js";
import { isWgslComparisonSampler, isWgslSampler } from "../../data/sampler.js";
import { accessModeMap, isWgslStorageTexture, isWgslTexture, } from "../../data/texture.js";
import { formatToWGSLType } from "../../data/vertexFormatData.js";
import { getName } from "../../shared/meta.js";
import { $internal } from "../../shared/symbols.js";
import { assertExhaustive } from "../../shared/utilityTypes.js";
import { isAttribute } from "../vertexLayout/connectAttributesToShader.js";
/**
* Schemas for which their `type` property directly
* translates to the resulting WGSL code.
*/
const identityTypes = [
'bool',
'f32',
'f16',
'i32',
'u32',
'vec2f',
'vec3f',
'vec4f',
'vec2h',
'vec3h',
'vec4h',
'vec2i',
'vec3i',
'vec4i',
'vec2u',
'vec3u',
'vec4u',
'vec2<bool>',
'vec3<bool>',
'vec4<bool>',
'mat2x2f',
'mat3x3f',
'mat4x4f',
'texture_external',
];
function isIdentityType(data) {
return identityTypes.includes(data.type);
}
/**
* Resolves a single property of a struct.
* @param ctx - The resolution context.
* @param key - The key of the property.
* @param property - The property itself.
*
* @returns The resolved property string.
*/
function resolveStructProperty(ctx, [key, property]) {
return ` ${getAttributesString(property)}${key}: ${ctx.resolve(property).value},\n`;
}
/**
* Resolves a struct and adds its declaration to the resolution context.
* @param ctx - The resolution context.
* @param struct - The struct to resolve.
*
* @returns The resolved struct name.
*/
function resolveStruct(ctx, struct) {
if (struct[$internal].isAbstruct) {
throw new Error('Cannot resolve abstract struct types to WGSL.');
}
const id = ctx.makeUniqueIdentifier(getName(struct), 'global');
ctx.addDeclaration(`\
struct ${id} {
${Object.entries(struct.propTypes)
.map((prop) => resolveStructProperty(ctx, prop))
.join('')}\
}`, id);
return id;
}
/**
* Resolves an unstruct (struct that does not align data by default) to its struct data counterpart.
* @param ctx - The resolution context.
* @param unstruct - The unstruct to resolve.
*
* @returns The resolved unstruct name.
*
* @example
* ```ts
* resolveUnstruct(ctx, {
* uv: d.float16x2, // -> d.vec2f after resolution
* color: d.snorm8x4, -> d.vec4f after resolution
* });
* ```
*/
function resolveUnstruct(ctx, unstruct) {
const id = ctx.makeUniqueIdentifier(getName(unstruct), 'global');
ctx.addDeclaration(`\
struct ${id} {
${Object.entries(unstruct.propTypes)
.map((prop) => isAttribute(prop[1])
? resolveStructProperty(ctx, [prop[0], formatToWGSLType[prop[1].format]])
: resolveStructProperty(ctx, prop))
.join('')}
}`, id);
return id;
}
/**
* Resolves an array.
* @param ctx - The resolution context.
* @param array - The array to resolve.
*
* @returns The resolved array name along with its element type and count (if not runtime-sized).
*
* @example
* ```ts
* resolveArray(ctx, d.arrayOf(d.u32, 0)); // 'array<u32>' (not a real pattern, a function is preferred)
* resolveArray(ctx, d.arrayOf(d.u32, 5)); // 'array<u32, 5>'
* ```
*/
function resolveArray(ctx, array) {
const element = ctx.resolve(array.elementType).value;
return array.elementCount === 0
? `array<${element}>`
: `array<${element}, ${array.elementCount}>`;
}
function resolveDisarray(ctx, disarray) {
const element = ctx.resolve(isAttribute(disarray.elementType)
? formatToWGSLType[disarray.elementType.format]
: disarray.elementType).value;
return disarray.elementCount === 0
? `array<${element}>`
: `array<${element}, ${disarray.elementCount}>`;
}
/**
* Resolves a WGSL data-type schema to a string.
* @param ctx - The resolution context.
* @param data - The data-type to resolve.
*
* @returns The resolved data-type string.
*/
export function resolveData(ctx, data) {
if (isLooseData(data)) {
if (data.type === 'unstruct') {
return resolveUnstruct(ctx, data);
}
if (data.type === 'disarray') {
return resolveDisarray(ctx, data);
}
if (data.type === 'loose-decorated') {
return ctx.resolve(isAttribute(data.inner) ? formatToWGSLType[data.inner.format] : data.inner)
.value;
}
return ctx.resolve(formatToWGSLType[data.type]).value;
}
if (isIdentityType(data)) {
return data.type;
}
if (data.type === 'struct') {
return resolveStruct(ctx, data);
}
if (data.type === 'array') {
return resolveArray(ctx, data);
}
if (data.type === 'atomic') {
return `atomic<${resolveData(ctx, data.inner)}>`;
}
if (data.type === 'decorated') {
return ctx.resolve(data.inner).value;
}
if (data.type === 'ptr') {
if (data.addressSpace === 'storage') {
return `ptr<storage, ${ctx.resolve(data.inner).value}, ${data.access === 'read-write' ? 'read_write' : data.access}>`;
}
return `ptr<${data.addressSpace}, ${ctx.resolve(data.inner).value}>`;
}
if (data.type === 'abstractInt' ||
data.type === 'abstractFloat' ||
data.type === 'void' ||
data.type === 'u16') {
throw new Error(`${data.type} has no representation in WGSL`);
}
if (isWgslStorageTexture(data)) {
return `${data.type}<${data.format}, ${accessModeMap[data.access]}>`;
}
if (isWgslTexture(data)) {
return data.type.startsWith('texture_depth')
? data.type
: `${data.type}<${data.sampleType.type}>`;
}
if (isWgslComparisonSampler(data) || isWgslSampler(data)) {
return data.type;
}
assertExhaustive(data, 'resolveData');
}