// @flow import {packUint8ToFloat} from '../shaders/encode_attribute'; import Color from '../style-spec/util/color'; import {supportsPropertyExpression} from '../style-spec/util/properties'; import {register} from '../util/web_worker_transfer'; import {PossiblyEvaluatedPropertyValue} from '../style/properties'; import {StructArrayLayout1f4, StructArrayLayout2f8, StructArrayLayout4f16, PatternLayoutArray} from './array_types'; import {clamp} from '../util/util'; import patternAttributes from './bucket/pattern_attributes'; 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 {CanonicalTileID} from '../source/tile_id'; 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'; import type {FormattedSection} from '../style-spec/expression/types/formatted'; export type BinderUniform = { name: string, property: string, binding: Uniform }; 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 AttributeBinder { populatePaintArray(length: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection): void; updatePaintArray(start: number, length: number, feature: Feature, featureState: FeatureState, imagePositions: {[_: string]: ImagePosition}): void; upload(Context): void; destroy(): void; } interface UniformBinder { uniformNames: Array; setUniform(uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue<*>, uniformName: string): void; getBinding(context: Context, location: WebGLUniformLocation, name: string): $Shape>; } class ConstantBinder implements UniformBinder { value: mixed; type: string; uniformNames: Array; constructor(value: mixed, names: Array, type: string) { this.value = value; this.uniformNames = names.map(name => `u_${name}`); this.type = type; } setUniform(uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue): void { uniform.set(currentValue.constantOr(this.value)); } getBinding(context: Context, location: WebGLUniformLocation, _: string): $Shape> { return (this.type === 'color') ? new UniformColor(context, location) : new Uniform1f(context, location); } } class CrossFadedConstantBinder implements UniformBinder { uniformNames: Array; patternFrom: ?Array; patternTo: ?Array; pixelRatioFrom: number; pixelRatioTo: number; constructor(value: mixed, names: Array) { this.uniformNames = names.map(name => `u_${name}`); this.patternFrom = null; this.patternTo = null; this.pixelRatioFrom = 1.0; this.pixelRatioTo = 1.0; } setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) { this.pixelRatioFrom = posFrom.pixelRatio; this.pixelRatioTo = posTo.pixelRatio; this.patternFrom = posFrom.tlbr; this.patternTo = posTo.tlbr; } setUniform(uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue, uniformName: string) { const pos = uniformName === 'u_pattern_to' ? this.patternTo : uniformName === 'u_pattern_from' ? this.patternFrom : uniformName === 'u_pixel_ratio_to' ? this.pixelRatioTo : uniformName === 'u_pixel_ratio_from' ? this.pixelRatioFrom : null; if (pos) uniform.set(pos); } getBinding(context: Context, location: WebGLUniformLocation, name: string): $Shape> { return name.substr(0, 9) === 'u_pattern' ? new Uniform4f(context, location) : new Uniform1f(context, location); } } class SourceExpressionBinder implements AttributeBinder { expression: SourceExpression; type: string; maxValue: number; paintVertexArray: StructArray; paintVertexAttributes: Array; paintVertexBuffer: ?VertexBuffer; constructor(expression: SourceExpression, names: Array, type: string, PaintVertexArray: Class) { this.expression = expression; this.type = type; this.maxValue = 0; this.paintVertexAttributes = names.map((name) => ({ name: `a_${name}`, type: 'Float32', components: type === 'color' ? 2 : 1, offset: 0 })); this.paintVertexArray = new PaintVertexArray(); } populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { const start = this.paintVertexArray.length; const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, canonical, [], formattedSection); this.paintVertexArray.resize(newLength); this._setPaintValue(start, newLength, value); } updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState) { const value = this.expression.evaluate({zoom: 0}, feature, featureState); this._setPaintValue(start, end, value); } _setPaintValue(start, end, value) { if (this.type === 'color') { const color = packColor(value); for (let i = start; i < end; i++) { this.paintVertexArray.emplace(i, color[0], color[1]); } } else { for (let i = start; i < end; i++) { this.paintVertexArray.emplace(i, value); } this.maxValue = Math.max(this.maxValue, Math.abs(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(); } } } class CompositeExpressionBinder implements AttributeBinder, UniformBinder { expression: CompositeExpression; uniformNames: Array; type: string; useIntegerZoom: boolean; zoom: number; maxValue: number; paintVertexArray: StructArray; paintVertexAttributes: Array; paintVertexBuffer: ?VertexBuffer; constructor(expression: CompositeExpression, names: Array, type: string, useIntegerZoom: boolean, zoom: number, PaintVertexArray: Class) { this.expression = expression; this.uniformNames = names.map(name => `u_${name}_t`); this.type = type; this.useIntegerZoom = useIntegerZoom; this.zoom = zoom; this.maxValue = 0; this.paintVertexAttributes = names.map((name) => ({ name: `a_${name}`, type: 'Float32', components: type === 'color' ? 4 : 2, offset: 0 })); this.paintVertexArray = new PaintVertexArray(); } populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, canonical, [], formattedSection); const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, canonical, [], formattedSection); const start = this.paintVertexArray.length; this.paintVertexArray.resize(newLength); this._setPaintValue(start, newLength, min, max); } updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState) { const min = this.expression.evaluate({zoom: this.zoom}, feature, featureState); const max = this.expression.evaluate({zoom: this.zoom + 1}, feature, featureState); this._setPaintValue(start, end, min, max); } _setPaintValue(start, end, min, max) { if (this.type === 'color') { const minColor = packColor(min); const maxColor = packColor(max); for (let i = start; i < end; i++) { this.paintVertexArray.emplace(i, minColor[0], minColor[1], maxColor[0], maxColor[1]); } } else { for (let i = start; i < end; i++) { this.paintVertexArray.emplace(i, min, max); } this.maxValue = Math.max(this.maxValue, Math.abs(min), Math.abs(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(); } } setUniform(uniform: Uniform<*>, globals: GlobalProperties): void { const currentZoom = this.useIntegerZoom ? Math.floor(globals.zoom) : globals.zoom; const factor = clamp(this.expression.interpolationFactor(currentZoom, this.zoom, this.zoom + 1), 0, 1); uniform.set(factor); } getBinding(context: Context, location: WebGLUniformLocation, _: string): Uniform1f { return new Uniform1f(context, location); } } class CrossFadedCompositeBinder implements AttributeBinder { expression: CompositeExpression; type: string; useIntegerZoom: boolean; zoom: number; layerId: string; zoomInPaintVertexArray: StructArray; zoomOutPaintVertexArray: StructArray; zoomInPaintVertexBuffer: ?VertexBuffer; zoomOutPaintVertexBuffer: ?VertexBuffer; paintVertexAttributes: Array; constructor(expression: CompositeExpression, type: string, useIntegerZoom: boolean, zoom: number, PaintVertexArray: Class, layerId: string) { this.expression = expression; this.type = type; this.useIntegerZoom = useIntegerZoom; this.zoom = zoom; this.layerId = layerId; this.zoomInPaintVertexArray = new PaintVertexArray(); this.zoomOutPaintVertexArray = new PaintVertexArray(); } populatePaintArray(length: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}) { const start = this.zoomInPaintVertexArray.length; this.zoomInPaintVertexArray.resize(length); this.zoomOutPaintVertexArray.resize(length); this._setPaintValues(start, length, feature.patterns && feature.patterns[this.layerId], imagePositions); } updatePaintArray(start: number, end: number, feature: Feature, featureState: FeatureState, imagePositions: {[_: string]: ImagePosition}) { this._setPaintValues(start, end, feature.patterns && feature.patterns[this.layerId], imagePositions); } _setPaintValues(start, end, patterns, positions) { if (!positions || !patterns) return; const {min, mid, max} = patterns; const imageMin = positions[min]; const imageMid = positions[mid]; const imageMax = positions[max]; if (!imageMin || !imageMid || !imageMax) return; // 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. for (let i = start; i < end; i++) { this.zoomInPaintVertexArray.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], imageMid.pixelRatio, imageMin.pixelRatio, ); this.zoomOutPaintVertexArray.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], imageMid.pixelRatio, imageMax.pixelRatio, ); } } upload(context: Context) { if (this.zoomInPaintVertexArray && this.zoomInPaintVertexArray.arrayBuffer && this.zoomOutPaintVertexArray && this.zoomOutPaintVertexArray.arrayBuffer) { this.zoomInPaintVertexBuffer = context.createVertexBuffer(this.zoomInPaintVertexArray, patternAttributes.members, this.expression.isStateDependent); this.zoomOutPaintVertexBuffer = context.createVertexBuffer(this.zoomOutPaintVertexArray, patternAttributes.members, this.expression.isStateDependent); } } destroy() { if (this.zoomOutPaintVertexBuffer) this.zoomOutPaintVertexBuffer.destroy(); if (this.zoomInPaintVertexBuffer) this.zoomInPaintVertexBuffer.destroy(); } } /** * 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]: (AttributeBinder | UniformBinder) }; cacheKey: string; _buffers: Array; constructor(layer: TypedStyleLayer, zoom: number, filterProperties: (_: string) => boolean) { this.binders = {}; this._buffers = []; 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 expression = value.value; const type = value.property.specification.type; const useIntegerZoom = value.property.useIntegerZoom; const propType = value.property.specification['property-type']; const isCrossFaded = propType === 'cross-faded' || propType === 'cross-faded-data-driven'; if (expression.kind === 'constant') { this.binders[property] = isCrossFaded ? new CrossFadedConstantBinder(expression.value, names) : new ConstantBinder(expression.value, names, type); keys.push(`/u_${property}`); } else if (expression.kind === 'source' || isCrossFaded) { const StructArrayLayout = layoutType(property, type, 'source'); this.binders[property] = isCrossFaded ? new CrossFadedCompositeBinder(expression, type, useIntegerZoom, zoom, StructArrayLayout, layer.id) : new SourceExpressionBinder(expression, names, type, StructArrayLayout); keys.push(`/a_${property}`); } else { const StructArrayLayout = layoutType(property, type, 'composite'); this.binders[property] = new CompositeExpressionBinder(expression, names, type, useIntegerZoom, zoom, StructArrayLayout); keys.push(`/z_${property}`); } } this.cacheKey = keys.sort().join(''); } getMaxValue(property: string): number { const binder = this.binders[property]; return binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder ? binder.maxValue : 0; } populatePaintArrays(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) (binder: AttributeBinder).populatePaintArray(newLength, feature, imagePositions, canonical, formattedSection); } } setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof CrossFadedConstantBinder) binder.setConstantPatternPositions(posTo, posFrom); } } updatePaintArrays(featureStates: FeatureStates, featureMap: FeaturePositionMap, vtLayer: VectorTileLayer, layer: TypedStyleLayer, imagePositions: {[_: string]: ImagePosition}): boolean { let dirty: boolean = false; for (const id in featureStates) { const positions = 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 SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) && (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: AttributeBinder).updatePaintArray(pos.start, pos.end, feature, featureStates[id], imagePositions); dirty = true; } } } } return dirty; } defines(): Array { const result = []; for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder) { result.push(...binder.uniformNames.map(name => `#define HAS_UNIFORM_${name}`)); } } return result; } getBinderAttributes(): Array { const result = []; for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder) { for (let i = 0; i < binder.paintVertexAttributes.length; i++) { result.push(binder.paintVertexAttributes[i].name); } } else if (binder instanceof CrossFadedCompositeBinder) { for (let i = 0; i < patternAttributes.members.length; i++) { result.push(patternAttributes.members[i].name); } } } return result; } getBinderUniforms(): Array { const uniforms = []; for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder || binder instanceof CompositeExpressionBinder) { for (const uniformName of binder.uniformNames) { uniforms.push(uniformName); } } } return uniforms; } getPaintVertexBuffers(): Array { return this._buffers; } getUniforms(context: Context, locations: UniformLocations): Array { const uniforms = []; for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder || binder instanceof CompositeExpressionBinder) { for (const name of binder.uniformNames) { if (locations[name]) { const binding = binder.getBinding(context, locations[name], name); uniforms.push({name, property, binding}); } } } } return uniforms; } setUniforms(context: Context, binderUniforms: Array, properties: PossiblyEvaluated, 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]: any).setUniform(binding, globals, properties.get(property), name); } } updatePaintBuffers(crossfade?: CrossfadeParameters) { this._buffers = []; for (const property in this.binders) { const binder = this.binders[property]; if (crossfade && binder instanceof CrossFadedCompositeBinder) { const patternVertexBuffer = crossfade.fromScale === 2 ? binder.zoomInPaintVertexBuffer : binder.zoomOutPaintVertexBuffer; if (patternVertexBuffer) this._buffers.push(patternVertexBuffer); } else if ((binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder) && binder.paintVertexBuffer) { this._buffers.push(binder.paintVertexBuffer); } } } upload(context: Context) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) binder.upload(context); } this.updatePaintBuffers(); } destroy() { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) binder.destroy(); } } } export class ProgramConfigurationSet { programConfigurations: {[_: string]: ProgramConfiguration}; needsUpload: boolean; _featureMap: FeaturePositionMap; _bufferOffset: number; constructor(layers: $ReadOnlyArray, zoom: number, filterProperties: (_: string) => boolean = () => true) { this.programConfigurations = {}; for (const layer of layers) { this.programConfigurations[layer.id] = new ProgramConfiguration(layer, zoom, filterProperties); } this.needsUpload = false; this._featureMap = new FeaturePositionMap(); this._bufferOffset = 0; } populatePaintArrays(length: number, feature: Feature, index: number, imagePositions: {[_: string]: ImagePosition}, canonical: CanonicalTileID, formattedSection?: FormattedSection) { for (const key in this.programConfigurations) { this.programConfigurations[key].populatePaintArrays(length, feature, imagePositions, canonical, formattedSection); } if (feature.id !== undefined) { this._featureMap.add(feature.id, index, this._bufferOffset, length); } this._bufferOffset = length; this.needsUpload = true; } updatePaintArrays(featureStates: FeatureStates, vtLayer: VectorTileLayer, layers: $ReadOnlyArray, imagePositions: {[_: string]: ImagePosition}) { for (const layer of layers) { this.needsUpload = this.programConfigurations[layer.id].updatePaintArrays(featureStates, this._featureMap, 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', 'pixel_ratio_to', 'pixel_ratio_from'], 'fill-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_from'], 'fill-extrusion-pattern': ['pattern_to', 'pattern_from', 'pixel_ratio_to', 'pixel_ratio_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);