UNPKG

typegpu

Version:

A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.

137 lines (136 loc) 5.72 kB
import { alignmentOf, customAlignmentOf } from "../../data/alignmentOf.js"; import { getCustomLocation, isLooseDecorated, isUnstruct } from "../../data/dataTypes.js"; import { sizeOf } from "../../data/sizeOf.js"; import { isDecorated, isWgslStruct } from "../../data/wgslTypes.js"; import { roundUp } from "../../mathUtils.js"; import { setName } from "../../shared/meta.js"; import { $internal, $soul } from "../../shared/symbols.js"; import { kindToDefaultFormatMap, vertexFormats, } from "../../shared/vertexFormat.js"; export function vertexLayout(schemaForCount, stepMode = 'vertex') { return new TgpuVertexLayoutImpl(schemaForCount, stepMode); } export function isVertexLayout(value) { return value?.resourceType === 'vertex-layout'; } // -------------- // Implementation // -------------- const defaultAttribEntry = Symbol('defaultAttribEntry'); function dataToContainedAttribs(layout, data, offset, customLocationMap, key) { if (isDecorated(data) || isLooseDecorated(data)) { const customLocation = getCustomLocation(data); if (customLocation !== undefined) { customLocationMap[key ?? defaultAttribEntry] = customLocation; } return dataToContainedAttribs(layout, data.inner, roundUp(offset, customAlignmentOf(data)), customLocationMap); } if (isWgslStruct(data)) { let memberOffset = offset; const propTypes = data.propTypes; return Object.fromEntries(Object.entries(propTypes).map(([key, value]) => { memberOffset = roundUp(memberOffset, alignmentOf(value)); const attrib = [ key, dataToContainedAttribs(layout, value, memberOffset, customLocationMap, key), ]; memberOffset += sizeOf(value); return attrib; })); } if (isUnstruct(data)) { let memberOffset = offset; const propTypes = data.propTypes; return Object.fromEntries(Object.entries(propTypes).map(([key, value]) => { memberOffset = roundUp(memberOffset, customAlignmentOf(value)); const attrib = [ key, dataToContainedAttribs(layout, value, memberOffset, customLocationMap, key), ]; memberOffset += sizeOf(value); return attrib; })); } if ('type' in data && typeof data.type === 'string') { if (vertexFormats.includes(data.type)) { return { _layout: layout, // hidden property, used to determine which buffers to apply when executing the pipeline format: data.type, offset, // oxlint-disable-next-line typescript/no-explicit-any -- too many type shenanigans }; } const format = kindToDefaultFormatMap[data.type]; if (format) { return { _layout: layout, // hidden property, used to determine which buffers to apply when executing the pipeline format, offset, // oxlint-disable-next-line typescript/no-explicit-any -- too many type shenanigans }; } } throw new Error(`Unsupported data used in vertex layout: ${String(data)}`); } class TgpuVertexLayoutImpl { [$internal] = true; [$soul]; resourceType = 'vertex-layout'; stride; attrib; schemaForCount; #customLocationMap = {}; constructor(schemaForCount, stepMode) { this.schemaForCount = schemaForCount; // `0` signals that the data-type is runtime-sized, and should not be used to create buffers. const arraySchema = schemaForCount(0); this[$soul] = { type: 'vertex-layout', schema: arraySchema, stepMode, label: undefined, }; this.stride = roundUp(sizeOf(arraySchema.elementType), alignmentOf(arraySchema)); this.attrib = dataToContainedAttribs(this, arraySchema.elementType, 0, this.#customLocationMap); } get stepMode() { return this[$soul].stepMode; } get vertexLayout() { // If defaultAttribEntry is in the custom location map, // it means that the vertex layout is based on a single attribute if (this.#customLocationMap[defaultAttribEntry] !== undefined) { if (typeof this.attrib.format !== 'string' || typeof this.attrib.offset !== 'number') { throw new Error('Single attribute vertex layouts must have a format and offset.'); } return { arrayStride: this.stride, stepMode: this.stepMode, attributes: [ { format: this.attrib.format, offset: this.attrib.offset, shaderLocation: this.#customLocationMap[defaultAttribEntry], }, ], }; } // check if all attributes have custom locations const allAttributesHaveCustomLocations = Object.keys(this.attrib).every((key) => this.#customLocationMap[key] !== undefined); if (!allAttributesHaveCustomLocations) { throw new Error('All attributes must have custom locations in order to unwrap a vertex layout.'); } return { arrayStride: this.stride, stepMode: this.stepMode, attributes: Object.entries(this.attrib).map(([key, attrib]) => ({ format: attrib.format, offset: attrib.offset, shaderLocation: this.#customLocationMap[key], })), }; } $name(label) { setName(this, label); return this; } }