UNPKG

ol

Version:

OpenLayers mapping library

237 lines (219 loc) • 7.79 kB
/** * Utilities for compiling expressions and turning them into WebGL concepts * @module ol/render/webgl/compileUtil */ import {asArray} from '../../color.js'; import { BooleanType, ColorType, newParsingContext, NumberArrayType, SizeType, StringType, } 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) * @param {import("../../expr/expression.js").ParsingContext} [parsingContext] Optional parsing context to be used; * if not specified, a new context using input variables from the compilation context will be used * @return {string} GLSL-compatible output */ export function expressionToGlsl( compilationContext, value, expectedType, parsingContext, ) { return buildExpression( value, expectedType, parsingContext ?? newParsingContext(compilationContext.inputVariables), 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; } if (type === StringType) { return 3; } return 1; } /** * @param {number} type Value type * @return {'float'|'vec2'|'vec3'|'vec4'} The corresponding GLSL type for this value */ export function getGlslTypeFromType(type) { if (type === StringType) { return 'float'; // strings take 3 floats in the attributes but only the first is used in the shader } 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 entry of context.variables.entries()) { const [varName, varType] = entry; const uniformName = uniformNameForVariable(varName); let glslType = getGlslTypeFromType(varType); if (varType === 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 entry of context.properties.entries()) { const [propName, propType] = entry; const glslType = getGlslTypeFromType(propType); const attributeName = `a_prop_${propName}`; if (propType === 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.js").UniformValue>} Uniforms */ export function generateUniformsFromContext(context, variables) { /** @type {Object<string,import("../../webgl/Helper.js").UniformValue>} */ const uniforms = {}; // define one uniform per variable for (const entry of context.variables.entries()) { const [varName, varType] = entry; const uniformName = uniformNameForVariable(varName); uniforms[uniformName] = () => { const value = variables[varName]; if (varType === BooleanType) { return value ? 1 : 0; } if (varType === ColorType) { const colorValue = /** @type {string|import('../../color.js').Color} */ (value); const color = [...asArray(colorValue || '#eee')]; color[0] /= 255; color[1] /= 255; color[2] /= 255; color[3] ??= 1; return color; } if (varType === StringType) { return getStringNumberEquivalent(/** @type {string} */ (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 entry of context.properties.entries()) { const [propName, propType] = entry; const callback = (feature) => { const value = feature.get(propName); if (propType === ColorType) { return packColor([...asArray(value || '#eee')]); } if (propType === BooleanType) { return value ? 1 : 0; } return value; }; attributes[`prop_${propName}`] = { size: getGlslSizeFromType(propType), callback, }; } return attributes; }