mapbox-gl
Version:
A WebGL interactive maps library
843 lines (721 loc) • 32.5 kB
JavaScript
// @flow
import { packUint8ToFloat } from '../shaders/encode_attribute';
import Color from '../style-spec/util/color';
import { supportsPropertyExpression } from '../style-spec/util/properties';
import { register, serialize, deserialize } from '../util/web_worker_transfer';
import { PossiblyEvaluatedPropertyValue } from '../style/properties';
import { StructArrayLayout1f4, StructArrayLayout2f8, StructArrayLayout4f16, PatternLayoutArray } from './array_types';
import EvaluationParameters from '../style/evaluation_parameters';
import FeaturePositionMap from './feature_position_map';
import {
Uniform,
Uniform1f,
UniformColor,
Uniform4f,
type UniformLocations
} from '../render/uniform_binding';
import type Context from '../gl/context';
import type {TypedStyleLayer} from '../style/style_layer/typed_style_layer';
import type {CrossfadeParameters} from '../style/evaluation_parameters';
import type {StructArray, StructArrayMember} from '../util/struct_array';
import type VertexBuffer from '../gl/vertex_buffer';
import type {ImagePosition} from '../render/image_atlas';
import type {
Feature,
FeatureState,
GlobalProperties,
SourceExpression,
CompositeExpression
} from '../style-spec/expression';
import type {PossiblyEvaluated} from '../style/properties';
import type {FeatureStates} from '../source/source_state';
export type BinderUniform = {
name: string,
property: string,
binding: Uniform<any>
};
function packColor(color: Color): [number, number] {
return [
packUint8ToFloat(255 * color.r, 255 * color.g),
packUint8ToFloat(255 * color.b, 255 * color.a)
];
}
/**
* `Binder` is the interface definition for the strategies for constructing,
* uploading, and binding paint property data as GLSL attributes. Most style-
* spec properties have a 1:1 relationship to shader attribute/uniforms, but
* some require multliple values per feature to be passed to the GPU, and in
* those cases we bind multiple attributes/uniforms.
*
* It has three implementations, one for each of the three strategies we use:
*
* * For _constant_ properties -- those whose value is a constant, or the constant
* result of evaluating a camera expression at a particular camera position -- we
* don't need a vertex attribute buffer, and instead use a uniform.
* * For data expressions, we use a vertex buffer with a single attribute value,
* the evaluated result of the source function for the given feature.
* * For composite expressions, we use a vertex buffer with two attributes: min and
* max values covering the range of zooms at which we expect the tile to be
* displayed. These values are calculated by evaluating the composite expression for
* the given feature at strategically chosen zoom levels. In addition to this
* attribute data, we also use a uniform value which the shader uses to interpolate
* between the min and max value at the final displayed zoom level. The use of a
* uniform allows us to cheaply update the value on every frame.
*
* Note that the shader source varies depending on whether we're using a uniform or
* attribute. We dynamically compile shaders at runtime to accomodate this.
*
* @private
*/
interface Binder<T> {
maxValue: number;
uniformNames: Array<string>;
populatePaintArray(length: number, feature: Feature, imagePositions: {[string]: ImagePosition}): void;
updatePaintArray(start: number, length: number, feature: Feature, featureState: FeatureState, imagePositions: {[string]: ImagePosition}): void;
upload(Context): void;
destroy(): void;
defines(): Array<string>;
setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition): void;
setUniforms(context: Context, uniform: Uniform<*>, globals: GlobalProperties,
currentValue: PossiblyEvaluatedPropertyValue<T>, uniformName: string): void;
getBinding(context: Context, location: WebGLUniformLocation): $Shape<Uniform<*>>;
}
class ConstantBinder<T> implements Binder<T> {
value: T;
names: Array<string>;
maxValue: number;
type: string;
uniformNames: Array<string>;
constructor(value: T, names: Array<string>, type: string) {
this.value = value;
this.names = names;
this.uniformNames = this.names.map(name => `u_${name}`);
this.type = type;
this.maxValue = -Infinity;
}
defines() {
return this.names.map(name => `#define HAS_UNIFORM_u_${name}`);
}
setConstantPatternPositions() {}
populatePaintArray() {}
updatePaintArray() {}
upload() {}
destroy() {}
setUniforms(context: Context, uniform: Uniform<*>, globals: GlobalProperties,
currentValue: PossiblyEvaluatedPropertyValue<T>): void {
uniform.set(currentValue.constantOr(this.value));
}
getBinding(context: Context, location: WebGLUniformLocation): $Shape<Uniform<any>> {
return (this.type === 'color') ?
new UniformColor(context, location) :
new Uniform1f(context, location);
}
static serialize(binder: ConstantBinder<T>) {
const {value, names, type} = binder;
return {value: serialize(value), names, type};
}
static deserialize(serialized: {value: T, names: Array<string>, type: string}) {
const {value, names, type} = serialized;
return new ConstantBinder(deserialize(value), names, type);
}
}
class CrossFadedConstantBinder<T> implements Binder<T> {
value: T;
names: Array<string>;
uniformNames: Array<string>;
patternPositions: {[string]: ?Array<number>};
type: string;
maxValue: number;
constructor(value: T, names: Array<string>, type: string) {
this.value = value;
this.names = names;
this.uniformNames = this.names.map(name => `u_${name}`);
this.type = type;
this.maxValue = -Infinity;
this.patternPositions = {patternTo: null, patternFrom: null};
}
defines() {
return this.names.map(name => `#define HAS_UNIFORM_u_${name}`);
}
populatePaintArray() {}
updatePaintArray() {}
upload() {}
destroy() {}
setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) {
this.patternPositions.patternTo = posTo.tlbr;
this.patternPositions.patternFrom = posFrom.tlbr;
}
setUniforms(context: Context, uniform: Uniform<*>, globals: GlobalProperties,
currentValue: PossiblyEvaluatedPropertyValue<T>, uniformName: string) {
const pos = this.patternPositions;
if (uniformName === "u_pattern_to" && pos.patternTo) uniform.set(pos.patternTo);
if (uniformName === "u_pattern_from" && pos.patternFrom) uniform.set(pos.patternFrom);
}
getBinding(context: Context, location: WebGLUniformLocation): $Shape<Uniform<any>> {
return new Uniform4f(context, location);
}
}
class SourceExpressionBinder<T> implements Binder<T> {
expression: SourceExpression;
names: Array<string>;
uniformNames: Array<string>;
type: string;
maxValue: number;
paintVertexArray: StructArray;
paintVertexAttributes: Array<StructArrayMember>;
paintVertexBuffer: ?VertexBuffer;
constructor(expression: SourceExpression, names: Array<string>, type: string, PaintVertexArray: Class<StructArray>) {
this.expression = expression;
this.names = names;
this.type = type;
this.uniformNames = this.names.map(name => `a_${name}`);
this.maxValue = -Infinity;
this.paintVertexAttributes = names.map((name) =>
({
name: `a_${name}`,
type: 'Float32',
components: type === 'color' ? 2 : 1,
offset: 0
})
);
this.paintVertexArray = new PaintVertexArray();
}
defines() {
return [];
}
setConstantPatternPositions() {}
populatePaintArray(newLength: number, feature: Feature) {
const paintArray = this.paintVertexArray;
const start = paintArray.length;
paintArray.reserve(newLength);
const value = this.expression.evaluate(new EvaluationParameters(0), feature, {});
if (this.type === 'color') {
const color = packColor(value);
for (let i = start; i < newLength; i++) {
paintArray.emplaceBack(color[0], color[1]);
}
} else {
for (let i = start; i < newLength; i++) {
paintArray.emplaceBack(value);
}
this.maxValue = Math.max(this.maxValue, value);
}
}
updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState) {
const paintArray = this.paintVertexArray;
const value = this.expression.evaluate({zoom: 0}, feature, featureState);
if (this.type === 'color') {
const color = packColor(value);
for (let i = start; i < end; i++) {
paintArray.emplace(i, color[0], color[1]);
}
} else {
for (let i = start; i < end; i++) {
paintArray.emplace(i, value);
}
this.maxValue = Math.max(this.maxValue, value);
}
}
upload(context: Context) {
if (this.paintVertexArray && this.paintVertexArray.arrayBuffer) {
if (this.paintVertexBuffer && this.paintVertexBuffer.buffer) {
this.paintVertexBuffer.updateData(this.paintVertexArray);
} else {
this.paintVertexBuffer = context.createVertexBuffer(this.paintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent);
}
}
}
destroy() {
if (this.paintVertexBuffer) {
this.paintVertexBuffer.destroy();
}
}
setUniforms(context: Context, uniform: Uniform<*>): void {
uniform.set(0);
}
getBinding(context: Context, location: WebGLUniformLocation): Uniform1f {
return new Uniform1f(context, location);
}
}
class CompositeExpressionBinder<T> implements Binder<T> {
expression: CompositeExpression;
names: Array<string>;
uniformNames: Array<string>;
type: string;
useIntegerZoom: boolean;
zoom: number;
maxValue: number;
paintVertexArray: StructArray;
paintVertexAttributes: Array<StructArrayMember>;
paintVertexBuffer: ?VertexBuffer;
constructor(expression: CompositeExpression, names: Array<string>, type: string, useIntegerZoom: boolean, zoom: number, layout: Class<StructArray>) {
this.expression = expression;
this.names = names;
this.uniformNames = this.names.map(name => `u_${name}_t`);
this.type = type;
this.useIntegerZoom = useIntegerZoom;
this.zoom = zoom;
this.maxValue = -Infinity;
const PaintVertexArray = layout;
this.paintVertexAttributes = names.map((name) => {
return {
name: `a_${name}`,
type: 'Float32',
components: type === 'color' ? 4 : 2,
offset: 0
};
});
this.paintVertexArray = new PaintVertexArray();
}
defines() {
return [];
}
setConstantPatternPositions() {}
populatePaintArray(newLength: number, feature: Feature) {
const paintArray = this.paintVertexArray;
const start = paintArray.length;
paintArray.reserve(newLength);
const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {});
const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {});
if (this.type === 'color') {
const minColor = packColor(min);
const maxColor = packColor(max);
for (let i = start; i < newLength; i++) {
paintArray.emplaceBack(minColor[0], minColor[1], maxColor[0], maxColor[1]);
}
} else {
for (let i = start; i < newLength; i++) {
paintArray.emplaceBack(min, max);
}
this.maxValue = Math.max(this.maxValue, min, max);
}
}
updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState) {
const paintArray = this.paintVertexArray;
const min = this.expression.evaluate({zoom: this.zoom }, feature, featureState);
const max = this.expression.evaluate({zoom: this.zoom + 1}, feature, featureState);
if (this.type === 'color') {
const minColor = packColor(min);
const maxColor = packColor(max);
for (let i = start; i < end; i++) {
paintArray.emplace(i, minColor[0], minColor[1], maxColor[0], maxColor[1]);
}
} else {
for (let i = start; i < end; i++) {
paintArray.emplace(i, min, max);
}
this.maxValue = Math.max(this.maxValue, min, max);
}
}
upload(context: Context) {
if (this.paintVertexArray && this.paintVertexArray.arrayBuffer) {
if (this.paintVertexBuffer && this.paintVertexBuffer.buffer) {
this.paintVertexBuffer.updateData(this.paintVertexArray);
} else {
this.paintVertexBuffer = context.createVertexBuffer(this.paintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent);
}
}
}
destroy() {
if (this.paintVertexBuffer) {
this.paintVertexBuffer.destroy();
}
}
interpolationFactor(currentZoom: number) {
if (this.useIntegerZoom) {
return this.expression.interpolationFactor(Math.floor(currentZoom), this.zoom, this.zoom + 1);
} else {
return this.expression.interpolationFactor(currentZoom, this.zoom, this.zoom + 1);
}
}
setUniforms(context: Context, uniform: Uniform<*>,
globals: GlobalProperties): void {
uniform.set(this.interpolationFactor(globals.zoom));
}
getBinding(context: Context, location: WebGLUniformLocation): Uniform1f {
return new Uniform1f(context, location);
}
}
class CrossFadedCompositeBinder<T> implements Binder<T> {
expression: CompositeExpression;
names: Array<string>;
uniformNames: Array<string>;
type: string;
useIntegerZoom: boolean;
zoom: number;
maxValue: number;
layerId: string;
zoomInPaintVertexArray: StructArray;
zoomOutPaintVertexArray: StructArray;
zoomInPaintVertexBuffer: ?VertexBuffer;
zoomOutPaintVertexBuffer: ?VertexBuffer;
paintVertexAttributes: Array<StructArrayMember>;
constructor(expression: CompositeExpression, names: Array<string>, type: string, useIntegerZoom: boolean, zoom: number, PaintVertexArray: Class<StructArray>, layerId: string) {
this.expression = expression;
this.names = names;
this.type = type;
this.uniformNames = this.names.map(name => `u_${name}_t`);
this.useIntegerZoom = useIntegerZoom;
this.zoom = zoom;
this.maxValue = -Infinity;
this.layerId = layerId;
this.paintVertexAttributes = names.map((name) =>
({
name: `a_${name}`,
type: 'Uint16',
components: 4,
offset: 0
})
);
this.zoomInPaintVertexArray = new PaintVertexArray();
this.zoomOutPaintVertexArray = new PaintVertexArray();
}
defines() {
return [];
}
setConstantPatternPositions() {}
populatePaintArray(length: number, feature: Feature, imagePositions: {[string]: ImagePosition}) {
// We populate two paint arrays because, for cross-faded properties, we don't know which direction
// we're cross-fading to at layout time. In order to keep vertex attributes to a minimum and not pass
// unnecessary vertex data to the shaders, we determine which to upload at draw time.
const zoomInArray = this.zoomInPaintVertexArray;
const zoomOutArray = this.zoomOutPaintVertexArray;
const { layerId } = this;
const start = zoomInArray.length;
zoomInArray.reserve(length);
zoomOutArray.reserve(length);
if (imagePositions && feature.patterns && feature.patterns[layerId]) {
const { min, mid, max } = feature.patterns[layerId];
const imageMin = imagePositions[min];
const imageMid = imagePositions[mid];
const imageMax = imagePositions[max];
if (!imageMin || !imageMid || !imageMax) return;
for (let i = start; i < length; i++) {
zoomInArray.emplaceBack(
imageMid.tl[0], imageMid.tl[1], imageMid.br[0], imageMid.br[1],
imageMin.tl[0], imageMin.tl[1], imageMin.br[0], imageMin.br[1]
);
zoomOutArray.emplaceBack(
imageMid.tl[0], imageMid.tl[1], imageMid.br[0], imageMid.br[1],
imageMax.tl[0], imageMax.tl[1], imageMax.br[0], imageMax.br[1]
);
}
}
}
updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState, imagePositions: {[string]: ImagePosition}) {
// We populate two paint arrays because, for cross-faded properties, we don't know which direction
// we're cross-fading to at layout time. In order to keep vertex attributes to a minimum and not pass
// unnecessary vertex data to the shaders, we determine which to upload at draw time.
const zoomInArray = this.zoomInPaintVertexArray;
const zoomOutArray = this.zoomOutPaintVertexArray;
const { layerId } = this;
if (imagePositions && feature.patterns && feature.patterns[layerId]) {
const {min, mid, max} = feature.patterns[layerId];
const imageMin = imagePositions[min];
const imageMid = imagePositions[mid];
const imageMax = imagePositions[max];
if (!imageMin || !imageMid || !imageMax) return;
for (let i = start; i < end; i++) {
zoomInArray.emplace(i,
imageMid.tl[0], imageMid.tl[1], imageMid.br[0], imageMid.br[1],
imageMin.tl[0], imageMin.tl[1], imageMin.br[0], imageMin.br[1]
);
zoomOutArray.emplace(i,
imageMid.tl[0], imageMid.tl[1], imageMid.br[0], imageMid.br[1],
imageMax.tl[0], imageMax.tl[1], imageMax.br[0], imageMax.br[1]
);
}
}
}
upload(context: Context) {
if (this.zoomInPaintVertexArray && this.zoomInPaintVertexArray.arrayBuffer && this.zoomOutPaintVertexArray && this.zoomOutPaintVertexArray.arrayBuffer) {
this.zoomInPaintVertexBuffer = context.createVertexBuffer(this.zoomInPaintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent);
this.zoomOutPaintVertexBuffer = context.createVertexBuffer(this.zoomOutPaintVertexArray, this.paintVertexAttributes, this.expression.isStateDependent);
}
}
destroy() {
if (this.zoomOutPaintVertexBuffer) this.zoomOutPaintVertexBuffer.destroy();
if (this.zoomInPaintVertexBuffer) this.zoomInPaintVertexBuffer.destroy();
}
setUniforms(context: Context, uniform: Uniform<*>): void {
uniform.set(0);
}
getBinding(context: Context, location: WebGLUniformLocation): $Shape<Uniform<any>> {
return new Uniform1f(context, location);
}
}
/**
* ProgramConfiguration contains the logic for binding style layer properties and tile
* layer feature data into GL program uniforms and vertex attributes.
*
* Non-data-driven property values are bound to shader uniforms. Data-driven property
* values are bound to vertex attributes. In order to support a uniform GLSL syntax over
* both, [Mapbox GL Shaders](https://github.com/mapbox/mapbox-gl-shaders) defines a `#pragma`
* abstraction, which ProgramConfiguration is responsible for implementing. At runtime,
* it examines the attributes of a particular layer, combines this with fixed knowledge
* about how layers of the particular type are implemented, and determines which uniforms
* and vertex attributes will be required. It can then substitute the appropriate text
* into the shader source code, create and link a program, and bind the uniforms and
* vertex attributes in preparation for drawing.
*
* When a vector tile is parsed, this same configuration information is used to
* populate the attribute buffers needed for data-driven styling using the zoom
* level and feature property data.
*
* @private
*/
export default class ProgramConfiguration {
binders: { [string]: Binder<any> };
cacheKey: string;
layoutAttributes: Array<StructArrayMember>;
_buffers: Array<VertexBuffer>;
_featureMap: FeaturePositionMap;
_bufferOffset: number;
constructor() {
this.binders = {};
this.cacheKey = '';
this._buffers = [];
this._featureMap = new FeaturePositionMap();
this._bufferOffset = 0;
}
static createDynamic<Layer: TypedStyleLayer>(layer: Layer, zoom: number, filterProperties: (string) => boolean) {
const self = new ProgramConfiguration();
const keys = [];
for (const property in layer.paint._values) {
if (!filterProperties(property)) continue;
const value = layer.paint.get(property);
if (!(value instanceof PossiblyEvaluatedPropertyValue) || !supportsPropertyExpression(value.property.specification)) {
continue;
}
const names = paintAttributeNames(property, layer.type);
const type = value.property.specification.type;
const useIntegerZoom = value.property.useIntegerZoom;
const isCrossFaded = value.property.specification['property-type'] === 'cross-faded' ||
value.property.specification['property-type'] === 'cross-faded-data-driven';
if (isCrossFaded) {
if (value.value.kind === 'constant') {
self.binders[property] = new CrossFadedConstantBinder(value.value.value, names, type);
keys.push(`/u_${property}`);
} else {
const StructArrayLayout = layoutType(property, type, 'source');
self.binders[property] = new CrossFadedCompositeBinder(value.value, names, type, useIntegerZoom, zoom, StructArrayLayout, layer.id);
keys.push(`/a_${property}`);
}
} else if (value.value.kind === 'constant') {
self.binders[property] = new ConstantBinder(value.value.value, names, type);
keys.push(`/u_${property}`);
} else if (value.value.kind === 'source') {
const StructArrayLayout = layoutType(property, type, 'source');
self.binders[property] = new SourceExpressionBinder(value.value, names, type, StructArrayLayout);
keys.push(`/a_${property}`);
} else {
const StructArrayLayout = layoutType(property, type, 'composite');
self.binders[property] = new CompositeExpressionBinder(value.value, names, type, useIntegerZoom, zoom, StructArrayLayout);
keys.push(`/z_${property}`);
}
}
self.cacheKey = keys.sort().join('');
return self;
}
populatePaintArrays(newLength: number, feature: Feature, index: number, imagePositions: {[string]: ImagePosition}) {
for (const property in this.binders) {
const binder = this.binders[property];
binder.populatePaintArray(newLength, feature, imagePositions);
}
if (feature.id !== undefined) {
this._featureMap.add(+feature.id, index, this._bufferOffset, newLength);
}
this._bufferOffset = newLength;
}
setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) {
for (const property in this.binders) {
const binder = this.binders[property];
binder.setConstantPatternPositions(posTo, posFrom);
}
}
updatePaintArrays(featureStates: FeatureStates, vtLayer: VectorTileLayer, layer: TypedStyleLayer, imagePositions: {[string]: ImagePosition}): boolean {
let dirty: boolean = false;
for (const id in featureStates) {
const positions = this._featureMap.getPositions(+id);
for (const pos of positions) {
const feature = vtLayer.feature(pos.index);
for (const property in this.binders) {
const binder = this.binders[property];
if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder) continue;
if ((binder: any).expression.isStateDependent === true) {
//AHM: Remove after https://github.com/mapbox/mapbox-gl-js/issues/6255
const value = layer.paint.get(property);
(binder: any).expression = value.value;
binder.updatePaintArray(pos.start, pos.end, feature, featureStates[id], imagePositions);
dirty = true;
}
}
}
}
return dirty;
}
defines(): Array<string> {
const result = [];
for (const property in this.binders) {
result.push(...this.binders[property].defines());
}
return result;
}
getPaintVertexBuffers(): Array<VertexBuffer> {
return this._buffers;
}
getUniforms(context: Context, locations: UniformLocations): Array<BinderUniform> {
const uniforms = [];
for (const property in this.binders) {
const binder = this.binders[property];
for (const name of binder.uniformNames) {
if (locations[name]) {
const binding = binder.getBinding(context, locations[name]);
uniforms.push({name, property, binding});
}
}
}
return uniforms;
}
setUniforms<Properties: Object>(context: Context, binderUniforms: Array<BinderUniform>, properties: PossiblyEvaluated<Properties>, globals: GlobalProperties) {
// Uniform state bindings are owned by the Program, but we set them
// from within the ProgramConfiguraton's binder members.
for (const {name, property, binding} of binderUniforms) {
this.binders[property].setUniforms(context, binding, globals, properties.get(property), name);
}
}
updatePatternPaintBuffers(crossfade: CrossfadeParameters) {
const buffers = [];
for (const property in this.binders) {
const binder = this.binders[property];
if (binder instanceof CrossFadedCompositeBinder) {
const patternVertexBuffer = crossfade.fromScale === 2 ? binder.zoomInPaintVertexBuffer : binder.zoomOutPaintVertexBuffer;
if (patternVertexBuffer) buffers.push(patternVertexBuffer);
} else if ((binder instanceof SourceExpressionBinder ||
binder instanceof CompositeExpressionBinder) &&
binder.paintVertexBuffer
) {
buffers.push(binder.paintVertexBuffer);
}
}
this._buffers = buffers;
}
upload(context: Context) {
for (const property in this.binders) {
this.binders[property].upload(context);
}
const buffers = [];
for (const property in this.binders) {
const binder = this.binders[property];
if ((binder instanceof SourceExpressionBinder ||
binder instanceof CompositeExpressionBinder) &&
binder.paintVertexBuffer
) {
buffers.push(binder.paintVertexBuffer);
}
}
this._buffers = buffers;
}
destroy() {
for (const property in this.binders) {
this.binders[property].destroy();
}
}
}
export class ProgramConfigurationSet<Layer: TypedStyleLayer> {
programConfigurations: {[string]: ProgramConfiguration};
needsUpload: boolean;
constructor(layoutAttributes: Array<StructArrayMember>, layers: $ReadOnlyArray<Layer>, zoom: number, filterProperties: (string) => boolean = () => true) {
this.programConfigurations = {};
for (const layer of layers) {
this.programConfigurations[layer.id] = ProgramConfiguration.createDynamic(layer, zoom, filterProperties);
this.programConfigurations[layer.id].layoutAttributes = layoutAttributes;
}
this.needsUpload = false;
}
populatePaintArrays(length: number, feature: Feature, index: number, imagePositions: {[string]: ImagePosition}) {
for (const key in this.programConfigurations) {
this.programConfigurations[key].populatePaintArrays(length, feature, index, imagePositions);
}
this.needsUpload = true;
}
updatePaintArrays(featureStates: FeatureStates, vtLayer: VectorTileLayer, layers: $ReadOnlyArray<TypedStyleLayer>, imagePositions: {[string]: ImagePosition}) {
for (const layer of layers) {
this.needsUpload = this.programConfigurations[layer.id].updatePaintArrays(featureStates, vtLayer, layer, imagePositions) || this.needsUpload;
}
}
get(layerId: string) {
return this.programConfigurations[layerId];
}
upload(context: Context) {
if (!this.needsUpload) return;
for (const layerId in this.programConfigurations) {
this.programConfigurations[layerId].upload(context);
}
this.needsUpload = false;
}
destroy() {
for (const layerId in this.programConfigurations) {
this.programConfigurations[layerId].destroy();
}
}
}
function paintAttributeNames(property, type) {
const attributeNameExceptions = {
'text-opacity': ['opacity'],
'icon-opacity': ['opacity'],
'text-color': ['fill_color'],
'icon-color': ['fill_color'],
'text-halo-color': ['halo_color'],
'icon-halo-color': ['halo_color'],
'text-halo-blur': ['halo_blur'],
'icon-halo-blur': ['halo_blur'],
'text-halo-width': ['halo_width'],
'icon-halo-width': ['halo_width'],
'line-gap-width': ['gapwidth'],
'line-pattern': ['pattern_to', 'pattern_from'],
'fill-pattern': ['pattern_to', 'pattern_from'],
'fill-extrusion-pattern': ['pattern_to', 'pattern_from'],
};
return attributeNameExceptions[property] ||
[property.replace(`${type}-`, '').replace(/-/g, '_')];
}
function getLayoutException(property) {
const propertyExceptions = {
'line-pattern':{
'source': PatternLayoutArray,
'composite': PatternLayoutArray
},
'fill-pattern': {
'source': PatternLayoutArray,
'composite': PatternLayoutArray
},
'fill-extrusion-pattern':{
'source': PatternLayoutArray,
'composite': PatternLayoutArray
}
};
return propertyExceptions[property];
}
function layoutType(property, type, binderType) {
const defaultLayouts = {
'color': {
'source': StructArrayLayout2f8,
'composite': StructArrayLayout4f16
},
'number': {
'source': StructArrayLayout1f4,
'composite': StructArrayLayout2f8
}
};
const layoutException = getLayoutException(property);
return layoutException && layoutException[binderType] ||
defaultLayouts[type][binderType];
}
register('ConstantBinder', ConstantBinder);
register('CrossFadedConstantBinder', CrossFadedConstantBinder);
register('SourceExpressionBinder', SourceExpressionBinder);
register('CrossFadedCompositeBinder', CrossFadedCompositeBinder);
register('CompositeExpressionBinder', CompositeExpressionBinder);
register('ProgramConfiguration', ProgramConfiguration, {omit: ['_buffers']});
register('ProgramConfigurationSet', ProgramConfigurationSet);