// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {log} from '../utils/log'; import type {PrimitiveDataType, NormalizedDataType} from '../shadertypes/data-types/data-types'; import type {AttributeShaderType} from '../shadertypes/shader-types/shader-types'; import type {VertexFormat} from '../shadertypes/vertex-types/vertex-formats'; import {shaderTypeDecoder} from '../shadertypes/shader-types/shader-type-decoder'; import {vertexFormatDecoder} from '../shadertypes/vertex-types/vertex-format-decoder'; import type {ShaderLayout, AttributeDeclaration} from '../adapter/types/shader-layout'; import type {BufferLayout} from '../adapter/types/buffer-layout'; /** Resolved info for a buffer / attribute combination to help backend configure it correctly */ export type AttributeInfo = { /** Attribute name */ attributeName: string; /** Location in shader */ location: number; /** Type / precision used in shader (buffer values may be converted) */ shaderType: AttributeShaderType; /** Calculations are done in this type in the shader's attribute declaration */ primitiveType: PrimitiveDataType; /** Components refer to the number of components in the shader's attribute declaration */ shaderComponents: 1 | 2 | 3 | 4; /** It is the shader attribute declaration that determines whether GPU will process as integer or float */ integer: boolean; /** BufferName */ bufferName: string; /** Format of buffer data */ vertexFormat: VertexFormat; /** Memory data type refers to the data type in the buffer */ bufferDataType: NormalizedDataType; /** Components refer to the number of components in the buffer's vertex format */ bufferComponents: 1 | 2 | 3 | 4; /** Normalization is encoded in the buffer layout's vertex format... */ normalized: boolean; /** If not specified, the step mode is inferred from the attribute name in the shader (contains string instance) */ stepMode: 'vertex' | 'instance'; /** The byteOffset is encoded in or calculated from the buffer layout */ byteOffset: number; /** The byteStride is encoded in or calculated from the buffer layout */ byteStride: number; }; type BufferAttributeInfo = { attributeName: string; bufferName: string; stepMode?: 'vertex' | 'instance'; vertexFormat: VertexFormat; byteOffset: number; byteStride: number; }; /** * Map from "attribute names" to "resolved attribute infos" * containing information about both buffer layouts and shader attribute declarations */ export function getAttributeInfosFromLayouts( shaderLayout: ShaderLayout, bufferLayout: BufferLayout[] ): Record { const attributeInfos: Record = {}; for (const attribute of shaderLayout.attributes) { const attributeInfo = getAttributeInfoFromLayouts(shaderLayout, bufferLayout, attribute.name); if (attributeInfo) { attributeInfos[attribute.name] = attributeInfo; } } return attributeInfos; } /** * Array indexed by "location" holding "resolved attribute infos" */ export function getAttributeInfosByLocation( shaderLayout: ShaderLayout, bufferLayout: BufferLayout[], maxVertexAttributes: number = 16 ): AttributeInfo[] { const attributeInfos = getAttributeInfosFromLayouts(shaderLayout, bufferLayout); const locationInfos: AttributeInfo[] = new Array(maxVertexAttributes).fill(null); for (const attributeInfo of Object.values(attributeInfos)) { locationInfos[attributeInfo.location] = attributeInfo; } return locationInfos; } /** * Get the combined information from a shader layout and a buffer layout for a specific attribute */ function getAttributeInfoFromLayouts( shaderLayout: ShaderLayout, bufferLayout: BufferLayout[], name: string ): AttributeInfo | null { const shaderDeclaration = getAttributeFromShaderLayout(shaderLayout, name); const bufferMapping: BufferAttributeInfo | null = getAttributeFromBufferLayout( bufferLayout, name ); // TODO should no longer happen if (!shaderDeclaration) { // || !bufferMapping return null; } const attributeTypeInfo = shaderTypeDecoder.getAttributeShaderTypeInfo(shaderDeclaration.type); const defaultVertexFormat = vertexFormatDecoder.getCompatibleVertexFormat(attributeTypeInfo); const vertexFormat = bufferMapping?.vertexFormat || defaultVertexFormat; const vertexFormatInfo = vertexFormatDecoder.getVertexFormatInfo(vertexFormat); return { attributeName: bufferMapping?.attributeName || shaderDeclaration.name, bufferName: bufferMapping?.bufferName || shaderDeclaration.name, location: shaderDeclaration.location, shaderType: shaderDeclaration.type, primitiveType: attributeTypeInfo.primitiveType, shaderComponents: attributeTypeInfo.components, vertexFormat, bufferDataType: vertexFormatInfo.type, bufferComponents: vertexFormatInfo.components, // normalized is a property of the buffer's vertex format normalized: vertexFormatInfo.normalized, // integer is a property of the shader declaration integer: attributeTypeInfo.integer, stepMode: bufferMapping?.stepMode || shaderDeclaration.stepMode || 'vertex', byteOffset: bufferMapping?.byteOffset || 0, byteStride: bufferMapping?.byteStride || 0 }; } function getAttributeFromShaderLayout( shaderLayout: ShaderLayout, name: string ): AttributeDeclaration | null { const attribute = shaderLayout.attributes.find(attr => attr.name === name); if (!attribute) { log.warn(`shader layout attribute "${name}" not present in shader`); } return attribute || null; } function getAttributeFromBufferLayout( bufferLayouts: BufferLayout[], name: string ): BufferAttributeInfo | null { // Check that bufferLayouts are valid (each either has format or attribute) checkBufferLayouts(bufferLayouts); let bufferLayoutInfo = getAttributeFromShortHand(bufferLayouts, name); if (bufferLayoutInfo) { return bufferLayoutInfo; } bufferLayoutInfo = getAttributeFromAttributesList(bufferLayouts, name); if (bufferLayoutInfo) { return bufferLayoutInfo; } // Didn't find... log.warn(`layout for attribute "${name}" not present in buffer layout`); return null; } /** Check that bufferLayouts are valid (each either has format or attribute) */ function checkBufferLayouts(bufferLayouts: BufferLayout[]) { for (const bufferLayout of bufferLayouts) { if ( (bufferLayout.attributes && bufferLayout.format) || (!bufferLayout.attributes && !bufferLayout.format) ) { log.warn(`BufferLayout ${name} must have either 'attributes' or 'format' field`); } } } /** Get attribute from format shorthand if specified */ function getAttributeFromShortHand( bufferLayouts: BufferLayout[], name: string ): BufferAttributeInfo | null { for (const bufferLayout of bufferLayouts) { if (bufferLayout.format && bufferLayout.name === name) { return { attributeName: bufferLayout.name, bufferName: name, stepMode: bufferLayout.stepMode, vertexFormat: bufferLayout.format, // If offset is needed, use `attributes` field. byteOffset: 0, byteStride: bufferLayout.byteStride || 0 }; } } return null; } /** * Search attribute mappings (e.g. interleaved attributes) for buffer mapping. * Not the name of the buffer might be the same as one of the interleaved attributes. */ function getAttributeFromAttributesList( bufferLayouts: BufferLayout[], name: string ): BufferAttributeInfo | null { for (const bufferLayout of bufferLayouts) { let byteStride: number | undefined = bufferLayout.byteStride; // Calculate a default byte stride if not provided if (typeof bufferLayout.byteStride !== 'number') { for (const attributeMapping of bufferLayout.attributes || []) { const info = vertexFormatDecoder.getVertexFormatInfo(attributeMapping.format); // @ts-ignore byteStride += info.byteLength; } } const attributeMapping = bufferLayout.attributes?.find(mapping => mapping.attribute === name); if (attributeMapping) { return { attributeName: attributeMapping.attribute, bufferName: bufferLayout.name, stepMode: bufferLayout.stepMode, vertexFormat: attributeMapping.format, byteOffset: attributeMapping.byteOffset, // @ts-ignore byteStride }; } } return null; }