UNPKG

@absulit/points

Version:

A Generative Art library made in WebGPU

389 lines (330 loc) 11.5 kB
/** * Utility types and methods to set wgsl types in memory. * This is mainly internal. * @module data-size * @ignore */ const size_2_align_2 = { size: 2, align: 2 }; const size_4_align_4 = { size: 4, align: 4 }; const size_6_align_8 = { size: 6, align: 8 }; const size_8_align_4 = { size: 8, align: 4 }; const size_8_align_8 = { size: 8, align: 8 }; const size_12_align_4 = { size: 12, align: 4 }; const size_12_align_16 = { size: 12, align: 16 }; const size_16_align_4 = { size: 16, align: 4 }; const size_16_align_16 = { size: 16, align: 16 }; const size_16_align_8 = { size: 16, align: 8 }; const size_24_align_8 = { size: 24, align: 8 }; const size_32_align_8 = { size: 32, align: 8 }; const size_32_align_16 = { size: 32, align: 16 }; const size_48_align_16 = { size: 48, align: 16 }; const size_64_align_16 = { size: 64, align: 16 }; export const typeSizes = { 'bool': size_4_align_4, 'i32': size_4_align_4, 'u32': size_4_align_4, 'f32': size_4_align_4, 'f16': size_2_align_2, 'atomic<u32>': size_4_align_4, 'atomic<i32>': size_4_align_4, 'vec2<bool>': size_8_align_8, 'vec2<i32>': size_8_align_8, 'vec2<u32>': size_8_align_8, 'vec2<f32>': size_8_align_8, // 'vec2<bool>': size_8_align_8, 'vec2i': size_8_align_8, 'vec2u': size_8_align_8, 'vec2f': size_8_align_8, 'vec2<f16>': size_4_align_4, 'vec2h': size_4_align_4, 'vec3<bool>': size_12_align_16, 'vec3<i32>': size_12_align_16, 'vec3<u32>': size_12_align_16, 'vec3<f32>': size_12_align_16, // 'vec3<bool>': size_12_align_16, 'vec3i': size_12_align_16, 'vec3u': size_12_align_16, 'vec3f': size_12_align_16, 'vec3<f16>': size_6_align_8, 'vec3h': size_6_align_8, 'vec4<bool>': size_16_align_16, 'vec4<i32>': size_16_align_16, 'vec4<u32>': size_16_align_16, 'vec4<f32>': size_16_align_16, // 'vec4<bool>': size_16_align_16, 'vec4i': size_16_align_16, 'vec4u': size_16_align_16, 'vec4f': size_16_align_16, 'vec4<f16>': size_8_align_8, 'vec4h': size_8_align_8, 'mat2x2<f32>': size_16_align_8, 'mat2x2f': size_16_align_8, 'mat2x2<f16>': size_8_align_4, 'mat2x2h': size_8_align_4, 'mat3x2<f32>': size_24_align_8, 'mat3x2f': size_24_align_8, 'mat3x2<f16>': size_12_align_4, 'mat3x2h': size_12_align_4, 'mat4x2<f32>': size_32_align_8, 'mat4x2f': size_32_align_8, 'mat4x2<f16>': size_16_align_4, 'mat4x2h': size_16_align_4, 'mat2x3<f32>': size_32_align_16, 'mat2x3f': size_32_align_16, 'mat2x3<f16>': size_16_align_8, 'mat2x3h': size_16_align_8, 'mat3x3<f32>': size_48_align_16, 'mat3x3f': size_48_align_16, 'mat3x3<f16>': size_24_align_8, 'mat3x3h': size_24_align_8, 'mat4x3<f32>': size_64_align_16, 'mat4x3f': size_64_align_16, 'mat4x3<f16>': size_32_align_8, 'mat4x3h': size_32_align_8, 'mat2x4<f32>': size_32_align_16, 'mat2x4f': size_32_align_16, 'mat2x4<f16>': size_16_align_8, 'mat2x4h': size_16_align_8, 'mat3x4<f32>': size_48_align_16, 'mat3x4f': size_48_align_16, 'mat3x4<f16>': size_24_align_8, 'mat3x4h': size_24_align_8, 'mat4x4<f32>': size_64_align_16, 'mat4x4f': size_64_align_16, 'mat4x4<f16>': size_32_align_8, 'mat4x4h': size_32_align_8, } // ignore comments const removeCommentsRE = /\/\*[\s\S]*?\*\/|\/\/.*/gim // struct name: const getStructNameRE = /struct\s+?(\w+)\s*{[^}]+}\n?/g // what's inside a struct: const insideStructRE = /struct\s+?\w+\s*{([^}]+)}\n?/g const arrayTypeAndAmountRE = /\s*<\s*([^,]+)\s*,?\s*(\d+)?\s*>/g const arrayIntegrityRE = /\s*(array\s*<\s*\w+\s*(?:,\s*\d+)?\s*>)\s*,?/g // you have to separete the result by splitting new lines function removeComments(value) { const matches = value.matchAll(removeCommentsRE); for (const match of matches) { const captured = match[0]; value = value.replace(captured, ''); } return value; } function getInsideStruct(value) { const matches = value.matchAll(insideStructRE); let lines = null; for (const match of matches) { lines = match[1].split('\n'); lines = lines.map(element => element.trim()) .filter(e => e !== ''); } return lines; } function getStructDataByName(value) { const matches = value.matchAll(getStructNameRE); let result = new Map(); for (const match of matches) { const captured = match[0]; const name = match[1]; const lines = getInsideStruct(captured); const types = lines.map(l => { const right = l.split(':')[1]; let type = ''; if (isArray(right)) { const arrayMatch = right.matchAll(arrayIntegrityRE); type = arrayMatch.next().value[1]; } else { type = right.split(',')[0].trim(); } return type; }); const names = lines.map(l => { const left = l.split(':')[0]; let name = ''; name = left.split(',')[0].trim(); return name; }); result.set(name, { captured, lines, types, unique_types: [...new Set(types)], names, }); } return result; } export function getArrayTypeAndAmount(value) { const matches = value.matchAll(arrayTypeAndAmountRE); let result = []; for (const match of matches) { const type = match[1]; const amount = match[2]; result.push({ type, amount: Number(amount) }); } return result; } /** * Check if string has 'array' in it * @param {String} value * @returns {boolean} */ export function isArray(value) { return value.indexOf('array') != -1; } export function getArrayAlign(structName, structData) { const [d] = getArrayTypeAndAmount(structName); const t = typeSizes[d.type] || structData.get(d.type); if (!t) { throw new Error(`${d.type} type has not been declared previously`) } // if it's not in typeSizes is a struct, //therefore probably stored in structData return t.align || t.maxAlign; } export function getArrayTypeData(currentType, structData) { const [d] = getArrayTypeAndAmount(currentType); if (!d) { throw `${currentType} seems to have an error, maybe a wrong amount?`; } if (d.amount == 0) { throw new Error(`${currentType} has an amount of 0`); } let currentTypeData = typeSizes[d.type] || structData.get(d.type); if (!currentTypeData) { throw `Struct or type '${d.type}' in ${currentType} is not defined.` } if (d.amount) { const t = typeSizes[d.type]; if (t) { // if array, the size is equal to the align currentTypeData = { size: t.align * d.amount, align: t.align }; } else { const sd = structData.get(d.type); if (sd) { currentTypeData = { size: sd.bytes * d.amount, align: sd.maxAlign }; } } } return currentTypeData; } /** * Calculates if there's a space of bytes left in the row * @param {Number} bytes current bytes size * @param {Number} maxSize max size of row, in this case probably 16 * @returns remaining bytes if any */ function getPadding(bytes, maxSize) { const remainder = bytes % maxSize let remainingBytes = 0; if (remainder) { remainingBytes = maxSize - remainder; } return remainingBytes } const MAX_ROW_SIZE = 16; const HALF = 2; export const dataSize = value => { const noCommentsValue = removeComments(value); const structData = getStructDataByName(noCommentsValue); structData.forEach(sd => { let bytes = 0; let remainingBytes = 0; sd.paddings = {}; sd.names.forEach((name, i) => { const type = sd.types[i]; let typeSize = typeSizes[type]; let repeat = 0; // if no typeSize is an array or struct if (!typeSize) { if (type) { if (isArray(type)) { const [innerType] = getArrayTypeAndAmount(type); typeSize = typeSizes[innerType.type]; repeat = innerType.amount; // check comment on top of do while innerType.align = MAX_ROW_SIZE; } else { const sd = structData.get(type); if (!sd) { throw `Type or struct ${type} doesn't exist.`; } typeSize = { size: sd.bytes, align: MAX_ROW_SIZE }; } } } const { size, align } = typeSize; const prevName = sd.names[i - 1]; /** * The idea with the repeat and the do while is that, if there's an * array, the subtype will be added `type.amount` times with the * same rules. That's it. */ do { let aligned = bytes % align === 0; while (!aligned) { remainingBytes -= HALF bytes += HALF; sd.paddings[prevName] ||= 0; sd.paddings[prevName] += HALF; aligned = bytes % align === 0; } if (remainingBytes && size > remainingBytes) { bytes += remainingBytes; sd.paddings[prevName] = remainingBytes; remainingBytes = 0; } bytes += size; repeat--; } while (repeat > 0) remainingBytes = getPadding(bytes, MAX_ROW_SIZE); }) remainingBytes = getPadding(bytes, MAX_ROW_SIZE); bytes += remainingBytes; sd.bytes = bytes; }) return structData } /** * Takes a number or array and infers the WGSL type * @param {Number|Array<Number>} value * @returns {String} WGSL equivalent type */ export function getWGSLType(value) { if ((!value && value !== 0) || ((Number.isNaN(value) || value instanceof Object || typeof value === 'string') && !(value instanceof Array))) { return ''; } const strValue = value.toString(); if (value instanceof Array) { return getArrayType(value); } const hasPeriod = strValue.indexOf('.') != -1; if (hasPeriod) { return 'f32'; } const hasSign = strValue.indexOf('-') != -1; if (hasSign) { return 'i32' } return 'u32'; } /** * From a JS value (number, array) * returns WGSL type like vec2f, vec3f * @param {Array|Object} value * @returns {String} */ export function getArrayType(value) { const isArray = Array.isArray(value); let type = null; if (isArray) { const { length } = value; if (length <= 4) { type = `vec${length}f`; } if (length > 4) { type = `array<f32, ${length}>`; } } return type; }