mapbox-gl
Version:
A WebGL interactive maps library
262 lines (221 loc) • 9.56 kB
JavaScript
// @flow
import {
prelude,
preludeFragPrecisionQualifiers,
preludeVertPrecisionQualifiers,
preludeTerrain,
preludeFog,
preludeCommonSource,
standardDerivativesExt
} from '../shaders/shaders.js';
import assert from 'assert';
import ProgramConfiguration from '../data/program_configuration.js';
import VertexArrayObject from './vertex_array_object.js';
import Context from '../gl/context.js';
import {terrainUniforms, globeUniforms} from '../terrain/terrain.js';
import type {TerrainUniformsType, GlobeUniformsType} from '../terrain/terrain.js';
import {fogUniforms} from './fog.js';
import type {FogUniformsType} from './fog.js';
import type SegmentVector from '../data/segment.js';
import type VertexBuffer from '../gl/vertex_buffer.js';
import type IndexBuffer from '../gl/index_buffer.js';
import type DepthMode from '../gl/depth_mode.js';
import type StencilMode from '../gl/stencil_mode.js';
import type ColorMode from '../gl/color_mode.js';
import type CullFaceMode from '../gl/cull_face_mode.js';
import type {UniformBindings, UniformValues} from './uniform_binding.js';
import type {BinderUniform} from '../data/program_configuration.js';
export type DrawMode =
| $PropertyType<WebGLRenderingContext, 'LINES'>
| $PropertyType<WebGLRenderingContext, 'TRIANGLES'>
| $PropertyType<WebGLRenderingContext, 'LINE_STRIP'>;
type ShaderSource = {
fragmentSource: string,
vertexSource: string,
staticAttributes: Array<string>,
usedDefines: Array<string>
};
function getTokenizedAttributes(array: Array<string>): Array<string> {
const result = [];
for (let i = 0; i < array.length; i++) {
if (array[i] === null) continue;
const token = array[i].split(' ');
result.push(token.pop());
}
return result;
}
class Program<Us: UniformBindings> {
program: WebGLProgram;
attributes: {[_: string]: number};
numAttributes: number;
fixedUniforms: Us;
binderUniforms: Array<BinderUniform>;
failedToCreate: boolean;
terrainUniforms: ?TerrainUniformsType;
fogUniforms: ?FogUniformsType;
globeUniforms: ?GlobeUniformsType;
static cacheKey(source: ShaderSource, name: string, defines: string[], programConfiguration: ?ProgramConfiguration): string {
let key = `${name}${programConfiguration ? programConfiguration.cacheKey : ''}`;
for (const define of defines) {
if (source.usedDefines.includes(define)) {
key += `/${define}`;
}
}
return key;
}
constructor(context: Context,
name: string,
source: ShaderSource,
configuration: ?ProgramConfiguration,
fixedUniforms: (Context) => Us,
fixedDefines: string[]) {
const gl = context.gl;
this.program = ((gl.createProgram(): any): WebGLProgram);
const staticAttrInfo = getTokenizedAttributes(source.staticAttributes);
const dynamicAttrInfo = configuration ? configuration.getBinderAttributes() : [];
const allAttrInfo = staticAttrInfo.concat(dynamicAttrInfo);
let defines = configuration ? configuration.defines() : [];
defines = defines.concat(fixedDefines.map((define) => `#define ${define}`));
const version = context.isWebGL2 ? '#version 300 es\n' : '';
const fragmentSource = version + defines.concat(
context.extStandardDerivatives && version.length === 0 ? standardDerivativesExt.concat(preludeFragPrecisionQualifiers) : preludeFragPrecisionQualifiers,
preludeFragPrecisionQualifiers,
preludeCommonSource,
prelude.fragmentSource,
preludeFog.fragmentSource,
source.fragmentSource).join('\n');
const vertexSource = version + defines.concat(
preludeVertPrecisionQualifiers,
preludeCommonSource,
prelude.vertexSource,
preludeFog.vertexSource,
preludeTerrain.vertexSource,
source.vertexSource).join('\n');
const fragmentShader = ((gl.createShader(gl.FRAGMENT_SHADER): any): WebGLShader);
if (gl.isContextLost()) {
this.failedToCreate = true;
return;
}
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
assert(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS), (gl.getShaderInfoLog(fragmentShader): any));
gl.attachShader(this.program, fragmentShader);
const vertexShader = ((gl.createShader(gl.VERTEX_SHADER): any): WebGLShader);
if (gl.isContextLost()) {
this.failedToCreate = true;
return;
}
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
assert(gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS), (gl.getShaderInfoLog(vertexShader): any));
gl.attachShader(this.program, vertexShader);
this.attributes = {};
this.numAttributes = allAttrInfo.length;
for (let i = 0; i < this.numAttributes; i++) {
if (allAttrInfo[i]) {
gl.bindAttribLocation(this.program, i, allAttrInfo[i]);
this.attributes[allAttrInfo[i]] = i;
}
}
gl.linkProgram(this.program);
assert(gl.getProgramParameter(this.program, gl.LINK_STATUS), (gl.getProgramInfoLog(this.program): any));
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
this.fixedUniforms = fixedUniforms(context);
this.binderUniforms = configuration ? configuration.getUniforms(context) : [];
if (fixedDefines.includes('TERRAIN')) {
this.terrainUniforms = terrainUniforms(context);
}
if (fixedDefines.includes('GLOBE')) {
this.globeUniforms = globeUniforms(context);
}
if (fixedDefines.includes('FOG')) {
this.fogUniforms = fogUniforms(context);
}
}
setTerrainUniformValues(context: Context, terrainUniformValues: UniformValues<TerrainUniformsType>) {
if (!this.terrainUniforms) return;
const uniforms: TerrainUniformsType = this.terrainUniforms;
if (this.failedToCreate) return;
context.program.set(this.program);
for (const name in terrainUniformValues) {
if (uniforms[name]) {
uniforms[name].set(this.program, name, terrainUniformValues[name]);
}
}
}
setGlobeUniformValues(context: Context, globeUniformValues: UniformValues<GlobeUniformsType>) {
if (!this.globeUniforms) return;
const uniforms: GlobeUniformsType = this.globeUniforms;
if (this.failedToCreate) return;
context.program.set(this.program);
for (const name in globeUniformValues) {
if (uniforms[name]) {
uniforms[name].set(this.program, name, globeUniformValues[name]);
}
}
}
setFogUniformValues(context: Context, fogUniformsValues: UniformValues<FogUniformsType>) {
if (!this.fogUniforms) return;
const uniforms: FogUniformsType = this.fogUniforms;
if (this.failedToCreate) return;
context.program.set(this.program);
for (const name in fogUniformsValues) {
uniforms[name].set(this.program, name, fogUniformsValues[name]);
}
}
draw(
context: Context,
drawMode: DrawMode,
depthMode: $ReadOnly<DepthMode>,
stencilMode: $ReadOnly<StencilMode>,
colorMode: $ReadOnly<ColorMode>,
cullFaceMode: $ReadOnly<CullFaceMode>,
uniformValues: UniformValues<Us>,
layerID: string,
layoutVertexBuffer: VertexBuffer,
indexBuffer: IndexBuffer,
segments: SegmentVector,
currentProperties: any,
zoom: ?number,
configuration: ?ProgramConfiguration,
dynamicLayoutBuffers: ?Array<?VertexBuffer>) {
const gl = context.gl;
if (this.failedToCreate) return;
context.program.set(this.program);
context.setDepthMode(depthMode);
context.setStencilMode(stencilMode);
context.setColorMode(colorMode);
context.setCullFace(cullFaceMode);
for (const name of Object.keys(this.fixedUniforms)) {
this.fixedUniforms[name].set(this.program, name, uniformValues[name]);
}
if (configuration) {
configuration.setUniforms(this.program, context, this.binderUniforms, currentProperties, {zoom: (zoom: any)});
}
const primitiveSize = {
[gl.LINES]: 2,
[gl.TRIANGLES]: 3,
[gl.LINE_STRIP]: 1
}[drawMode];
for (const segment of segments.get()) {
const vaos = segment.vaos || (segment.vaos = {});
const vao: VertexArrayObject = vaos[layerID] || (vaos[layerID] = new VertexArrayObject());
vao.bind(
context,
this,
layoutVertexBuffer,
configuration ? configuration.getPaintVertexBuffers() : [],
indexBuffer,
segment.vertexOffset,
dynamicLayoutBuffers ? dynamicLayoutBuffers : []
);
gl.drawElements(
drawMode,
segment.primitiveLength * primitiveSize,
gl.UNSIGNED_SHORT,
segment.primitiveOffset * primitiveSize * 2);
}
}
}
export default Program;