UNPKG

ol

Version:

OpenLayers mapping library

227 lines (209 loc) • 7.45 kB
/** * Utilities for compiling expressions and turning them into WebGL concepts * @module ol/render/webgl/compileUtil */ import {asArray} from '../../color.js'; import { ColorType, NumberArrayType, SizeType, newParsingContext, } from '../../expr/expression.js'; import { buildExpression, getStringNumberEquivalent, uniformNameForVariable, } from '../../expr/gpu.js'; /** * Recursively parses a style expression and outputs a GLSL-compatible string. Takes in a compilation context that * will be read and modified during the parsing operation. * @param {import("../../expr/gpu.js").CompilationContext} compilationContext Compilation context * @param {import("../../expr/expression.js").EncodedExpression} value Value * @param {number} [expectedType] Expected final type (can be several types combined) * @return {string} GLSL-compatible output */ export function expressionToGlsl(compilationContext, value, expectedType) { const parsingContext = newParsingContext(); return buildExpression( value, expectedType, parsingContext, compilationContext, ); } /** * Packs all components of a color into a two-floats array * @param {import("../../color.js").Color|string} color Color as array of numbers or string * @return {Array<number>} Vec2 array containing the color in compressed form */ export function packColor(color) { const array = asArray(color); const r = array[0] * 256; const g = array[1]; const b = array[2] * 256; const a = Math.round(array[3] * 255); return [r + g, b + a]; } /** * Unpacks a color from a packed color in two-floats array form * NOTE: this is only used for testing purposes * @param {Array<number>} packedColor Packed color generated by the `packColor` function * @return {Array<number>} Resulting unpacked color in array form with components in the range [0, 1] */ export function unpackColor(packedColor) { return [ Math.min(Math.floor(packedColor[0] / 256.0) / 255.0, 1), Math.min((packedColor[0] % 256.0) / 255.0, 1), Math.min(Math.floor(packedColor[1] / 256.0) / 255.0, 1), Math.min((packedColor[1] % 256.0) / 255.0, 1), ]; } /** * Equivalent of `unpackColor()` in GLSL * @type {string} */ export const UNPACK_COLOR_FN = `vec4 unpackColor(vec2 packedColor) { return vec4( min(floor(packedColor[0] / 256.0) / 255.0, 1.0), min(mod(packedColor[0], 256.0) / 255.0, 1.0), min(floor(packedColor[1] / 256.0) / 255.0, 1.0), min(mod(packedColor[1], 256.0) / 255.0, 1.0) ); }`; /** * @param {number} type Value type * @return {1|2|3|4} The amount of components for this value */ export function getGlslSizeFromType(type) { if (type === ColorType || type === SizeType) { return 2; } if (type === NumberArrayType) { return 4; } return 1; } /** * @param {number} type Value type * @return {'float'|'vec2'|'vec3'|'vec4'} The corresponding GLSL type for this value */ export function getGlslTypeFromType(type) { const size = getGlslSizeFromType(type); if (size > 1) { return /** @type {'vec2'|'vec3'|'vec4'} */ (`vec${size}`); } return 'float'; } /** * Applies the properties and variables collected in a compilation context to a ShaderBuilder instance: * properties will show up as attributes in shaders, and variables will show up as uniforms. * @param {import("./ShaderBuilder.js").ShaderBuilder} builder Shader builder * @param {import("../../expr/gpu.js").CompilationContext} context Compilation context */ export function applyContextToBuilder(builder, context) { // define one uniform per variable for (const varName in context.variables) { const variable = context.variables[varName]; const uniformName = uniformNameForVariable(variable.name); let glslType = getGlslTypeFromType(variable.type); if (variable.type === ColorType) { // we're not packing colors when they're passed as uniforms glslType = 'vec4'; } builder.addUniform(uniformName, glslType); } // for each feature attribute used in the fragment shader, define a varying that will be used to pass data // from the vertex to the fragment shader, as well as an attribute in the vertex shader (if not already present) for (const propName in context.properties) { const property = context.properties[propName]; const glslType = getGlslTypeFromType(property.type); const attributeName = `a_prop_${property.name}`; if (property.type === ColorType) { builder.addAttribute( attributeName, glslType, `unpackColor(${attributeName})`, 'vec4', ); } else { builder.addAttribute(attributeName, glslType); } } // add functions that were collected in the compilation contexts for (const functionName in context.functions) { builder.addVertexShaderFunction(context.functions[functionName]); builder.addFragmentShaderFunction(context.functions[functionName]); } } /** * Generates a set of uniforms from variables collected in a compilation context, * to be fed to a WebGLHelper instance * @param {import("../../expr/gpu.js").CompilationContext} context Compilation context * @param {import('../../style/flat.js').StyleVariables} [variables] Style variables. * @return {Object<string,import("../../webgl/Helper").UniformValue>} Uniforms */ export function generateUniformsFromContext(context, variables) { /** @type {Object<string,import("../../webgl/Helper").UniformValue>} */ const uniforms = {}; // define one uniform per variable for (const varName in context.variables) { const variable = context.variables[varName]; const uniformName = uniformNameForVariable(variable.name); uniforms[uniformName] = () => { const value = variables[variable.name]; if (typeof value === 'number') { return value; } if (typeof value === 'boolean') { return value ? 1 : 0; } if (variable.type === ColorType) { const color = [...asArray(value || '#eee')]; color[0] /= 255; color[1] /= 255; color[2] /= 255; color[3] ??= 1; return color; } if (typeof value === 'string') { return getStringNumberEquivalent(value); } return value; }; } return uniforms; } /** * Generates a set of attributes from properties collected in a compilation context, * to be fed to a WebGLHelper instance * @param {import("../../expr/gpu.js").CompilationContext} context Compilation context * @return {import('./VectorStyleRenderer.js').AttributeDefinitions} Attributes */ export function generateAttributesFromContext(context) { /** * @type {import('./VectorStyleRenderer.js').AttributeDefinitions} */ const attributes = {}; // Define attributes with their callback for each property used in the vertex shader for (const propName in context.properties) { const property = context.properties[propName]; const callback = (feature) => { const value = feature.get(property.name); if (property.type === ColorType) { return packColor([...asArray(value || '#eee')]); } if (typeof value === 'string') { return getStringNumberEquivalent(value); } if (typeof value === 'boolean') { return value ? 1 : 0; } return value; }; attributes[`prop_${property.name}`] = { size: getGlslSizeFromType(property.type), callback, }; } return attributes; }