ol
Version:
OpenLayers mapping library
227 lines (209 loc) • 7.45 kB
JavaScript
/**
* 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;
}