Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance improvement and reduced shader workload with globe projection #12039

Merged
merged 11 commits into from
Jul 18, 2022
3 changes: 2 additions & 1 deletion debug/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
zoom: 12.5,
center: [-122.4194, 37.7749],
style: 'mapbox://styles/mapbox/streets-v11',
hash: true
hash: true,
projection: 'globe'
});

</script>
Expand Down
46 changes: 21 additions & 25 deletions src/data/program_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import {
Uniform,
Uniform1f,
UniformColor,
Uniform4f,
type UniformLocations
Uniform4f
} from '../render/uniform_binding.js';

import type {CanonicalTileID} from '../source/tile_id.js';
Expand Down Expand Up @@ -88,8 +87,8 @@ interface AttributeBinder {

interface UniformBinder {
uniformNames: Array<string>;
setUniform(uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue<*>, uniformName: string): void;
getBinding(context: Context, location: WebGLUniformLocation, name: string): $Shape<Uniform<*>>;
setUniform(program: WebGLProgram, uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue<*>, uniformName: string): void;
getBinding(context: Context, name: string): $Shape<Uniform<*>>;
}

class ConstantBinder implements UniformBinder {
Expand All @@ -103,14 +102,14 @@ class ConstantBinder implements UniformBinder {
this.type = type;
}

setUniform(uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue<mixed>): void {
uniform.set(currentValue.constantOr(this.value));
setUniform(program: WebGLProgram, uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue<mixed>, uniformName: string): void {
uniform.set(program, uniformName, currentValue.constantOr(this.value));
}

getBinding(context: Context, location: WebGLUniformLocation, _: string): $Shape<Uniform<any>> {
getBinding(context: Context, _: string): $Shape<Uniform<any>> {
return (this.type === 'color') ?
new UniformColor(context, location) :
new Uniform1f(context, location);
new UniformColor(context) :
new Uniform1f(context);
}
}

Expand All @@ -136,19 +135,19 @@ class CrossFadedConstantBinder implements UniformBinder {
this.patternTo = posTo.tl.concat(posTo.br);
}

setUniform(uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue<mixed>, uniformName: string) {
setUniform(program: WebGLProgram, uniform: Uniform<*>, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue<mixed>, uniformName: string) {
const pos =
uniformName === 'u_pattern_to' || uniformName === 'u_dash_to' ? this.patternTo :
uniformName === 'u_pattern_from' || uniformName === 'u_dash_from' ? this.patternFrom :
uniformName === 'u_pixel_ratio_to' ? this.pixelRatioTo :
uniformName === 'u_pixel_ratio_from' ? this.pixelRatioFrom : null;
if (pos) uniform.set(pos);
if (pos) uniform.set(program, uniformName, pos);
}

getBinding(context: Context, location: WebGLUniformLocation, name: string): $Shape<Uniform<any>> {
getBinding(context: Context, name: string): $Shape<Uniform<any>> {
return name === 'u_pattern_from' || name === 'u_pattern_to' || name === 'u_dash_from' || name === 'u_dash_to' ?
new Uniform4f(context, location) :
new Uniform1f(context, location);
new Uniform4f(context) :
new Uniform1f(context);
}
}

Expand Down Expand Up @@ -291,14 +290,14 @@ class CompositeExpressionBinder implements AttributeBinder, UniformBinder {
}
}

setUniform(uniform: Uniform<*>, globals: GlobalProperties): void {
setUniform(program: WebGLProgram, uniform: Uniform<*>, globals: GlobalProperties, _: PossiblyEvaluatedPropertyValue<*>, uniformName: string): 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);
uniform.set(program, uniformName, factor);
}

getBinding(context: Context, location: WebGLUniformLocation, _: string): Uniform1f {
return new Uniform1f(context, location);
getBinding(context: Context, _: string): Uniform1f {
return new Uniform1f(context);
}
}

Expand Down Expand Up @@ -536,27 +535,24 @@ export default class ProgramConfiguration {
return this._buffers;
}

getUniforms(context: Context, locations: UniformLocations): Array<BinderUniform> {
getUniforms(context: Context): Array<BinderUniform> {
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});
}
uniforms.push({name, property, binding: binder.getBinding(context, name)});
}
}
}
return uniforms;
}

setUniforms<Properties: Object>(context: Context, binderUniforms: Array<BinderUniform>, properties: PossiblyEvaluated<Properties>, globals: GlobalProperties) {
setUniforms<Properties: Object>(program: WebGLProgram, 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 ProgramConfiguration's binder members.
for (const {name, property, binding} of binderUniforms) {
(this.binders[property]: any).setUniform(binding, globals, properties.get(property), name);
(this.binders[property]: any).setUniform(program, binding, globals, properties.get(property), name);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/render/draw_fill_extrusion.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ function drawExtrusionTiles(painter, source, layer, coords, depthMode, stencilMo
const baseDefines = ([]: any);
if (isGlobeProjection) {
baseDefines.push('PROJECTION_GLOBE_VIEW');
if (painter.style.terrainSetForDrapingOnly()) {
baseDefines.push('TERRAIN');
}
}
if (ao[0] > 0) { // intensity
baseDefines.push('FAUX_AO');
Expand Down
2 changes: 1 addition & 1 deletion src/render/draw_symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
const invMatrix = bucket.getProjection().createInversionMatrix(tr, coord.canonical);

const baseDefines = ([]: any);
if (painter.terrain && pitchWithMap) {
if (painter.terrainRenderModeElevated() && pitchWithMap) {
baseDefines.push('PITCH_WITH_MAP_TERRAIN');
}
if (bucketIsGlobeProjection) {
Expand Down
32 changes: 16 additions & 16 deletions src/render/fog.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow

import Context from '../gl/context.js';
import type {UniformLocations, UniformValues} from './uniform_binding.js';
import type {UniformValues} from './uniform_binding.js';
import type {UnwrappedTileID} from '../source/tile_id.js';
import Painter from './painter.js';
import Fog from '../style/fog.js';
Expand All @@ -25,21 +25,21 @@ export type FogUniformsType = {|
'u_viewport': Uniform2f,
|};

export const fogUniforms = (context: Context, locations: UniformLocations): FogUniformsType => ({
'u_fog_matrix': new UniformMatrix4f(context, locations.u_fog_matrix),
'u_fog_range': new Uniform2f(context, locations.u_fog_range),
'u_fog_color': new Uniform4f(context, locations.u_fog_color),
'u_fog_horizon_blend': new Uniform1f(context, locations.u_fog_horizon_blend),
'u_fog_temporal_offset': new Uniform1f(context, locations.u_fog_temporal_offset),
'u_frustum_tl': new Uniform3f(context, locations.u_frustum_tl),
'u_frustum_tr': new Uniform3f(context, locations.u_frustum_tr),
'u_frustum_br': new Uniform3f(context, locations.u_frustum_br),
'u_frustum_bl': new Uniform3f(context, locations.u_frustum_bl),
'u_globe_pos': new Uniform3f(context, locations.u_globe_pos),
'u_globe_radius': new Uniform1f(context, locations.u_globe_radius),
'u_globe_transition': new Uniform1f(context, locations.u_globe_transition),
'u_is_globe': new Uniform1i(context, locations.u_is_globe),
'u_viewport': new Uniform2f(context, locations.u_viewport)
export const fogUniforms = (context: Context): FogUniformsType => ({
'u_fog_matrix': new UniformMatrix4f(context),
'u_fog_range': new Uniform2f(context),
'u_fog_color': new Uniform4f(context),
'u_fog_horizon_blend': new Uniform1f(context),
'u_fog_temporal_offset': new Uniform1f(context),
'u_frustum_tl': new Uniform3f(context),
'u_frustum_tr': new Uniform3f(context),
'u_frustum_br': new Uniform3f(context),
'u_frustum_bl': new Uniform3f(context),
'u_globe_pos': new Uniform3f(context),
'u_globe_radius': new Uniform1f(context),
'u_globe_transition': new Uniform1f(context),
'u_is_globe': new Uniform1i(context),
'u_viewport': new Uniform2f(context)
});

export const fogUniformValues = (
Expand Down
10 changes: 7 additions & 3 deletions src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,11 @@ class Painter {
return !imagePosA || !imagePosB;
}

terrainRenderModeElevated(): boolean {
// Whether elevation sampling should be enabled in the vertex shader.
return this.style && !!this.style.getTerrain() && !!this.terrain && !this.terrain.renderingToTexture;
}

/**
* Returns #defines that would need to be injected into every Program
* based on the current state of Painter.
Expand All @@ -854,12 +859,11 @@ class Painter {
* @private
*/
currentGlobalDefines(): string[] {
const terrain = this.terrain && !this.terrain.renderingToTexture; // Enables elevation sampling in vertex shader.
const rtt = this.terrain && this.terrain.renderingToTexture;
const fog = this.style && this.style.fog;
const defines = [];

if (terrain) defines.push('TERRAIN');
if (this.terrainRenderModeElevated()) defines.push('TERRAIN');
// When terrain is active, fog is rendered as part of draping, not as part of tile
// rendering. Removing the fog flag during tile rendering avoids additional defines.
if (fog && !rtt && fog.getOpacity(this.transform.pitch) !== 0.0) {
Expand All @@ -876,7 +880,7 @@ class Painter {

const globalDefines = this.currentGlobalDefines();
const allDefines = globalDefines.concat(defines);
const key = Program.cacheKey(name, allDefines, programConfiguration);
const key = Program.cacheKey(shaders[name], name, allDefines, programConfiguration);

if (!this.cache[key]) {
this.cache[key] = new Program(this.context, name, shaders[name], programConfiguration, programUniforms[name], allDefines);
Expand Down
63 changes: 26 additions & 37 deletions src/render/program.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,22 @@ 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, UniformLocations} from './uniform_binding.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'>;

function getTokenizedAttributesAndUniforms (array: Array<string>): Array<string> {
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++) {
Expand All @@ -43,6 +50,7 @@ function getTokenizedAttributesAndUniforms (array: Array<string>): Array<string>
}
return result;
}

class Program<Us: UniformBindings> {
program: WebGLProgram;
attributes: {[_: string]: number};
Expand All @@ -53,36 +61,29 @@ class Program<Us: UniformBindings> {
terrainUniforms: ?TerrainUniformsType;
fogUniforms: ?FogUniformsType;

static cacheKey(name: string, defines: string[], programConfiguration: ?ProgramConfiguration): string {
static cacheKey(source: ShaderSource, name: string, defines: string[], programConfiguration: ?ProgramConfiguration): string {
let key = `${name}${programConfiguration ? programConfiguration.cacheKey : ''}`;
for (const define of defines) {
key += `/${define}`;
if (source.usedDefines.includes(define)) {
key += `/${define}`;
}
}
return key;
}

constructor(context: Context,
name: string,
source: {fragmentSource: string, vertexSource: string, staticAttributes: Array<string>, staticUniforms: Array<string>},
source: ShaderSource,
configuration: ?ProgramConfiguration,
fixedUniforms: (Context, UniformLocations) => Us,
fixedUniforms: (Context) => Us,
fixedDefines: string[]) {
const gl = context.gl;
this.program = gl.createProgram();

const staticAttrInfo = getTokenizedAttributesAndUniforms(source.staticAttributes);
const staticAttrInfo = getTokenizedAttributes(source.staticAttributes);
const dynamicAttrInfo = configuration ? configuration.getBinderAttributes() : [];
const allAttrInfo = staticAttrInfo.concat(dynamicAttrInfo);

const staticUniformsInfo = source.staticUniforms ? getTokenizedAttributesAndUniforms(source.staticUniforms) : [];
const dynamicUniformsInfo = configuration ? configuration.getBinderUniforms() : [];
// remove duplicate uniforms
const uniformList = staticUniformsInfo.concat(dynamicUniformsInfo);
const allUniformsInfo = [];
for (const uniform of uniformList) {
if (allUniformsInfo.indexOf(uniform) < 0) allUniformsInfo.push(uniform);
}

let defines = configuration ? configuration.defines() : [];
defines = defines.concat(fixedDefines.map((define) => `#define ${define}`));

Expand All @@ -100,6 +101,7 @@ class Program<Us: UniformBindings> {
preludeFog.vertexSource,
preludeTerrain.vertexSource,
source.vertexSource).join('\n');

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
if (gl.isContextLost()) {
this.failedToCreate = true;
Expand All @@ -121,7 +123,6 @@ class Program<Us: UniformBindings> {
gl.attachShader(this.program, vertexShader);

this.attributes = {};
const uniformLocations = {};

this.numAttributes = allAttrInfo.length;

Expand All @@ -138,23 +139,13 @@ class Program<Us: UniformBindings> {
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);

for (let it = 0; it < allUniformsInfo.length; it++) {
const uniform = allUniformsInfo[it];
if (uniform && !uniformLocations[uniform]) {
const uniformLocation = gl.getUniformLocation(this.program, uniform);
if (uniformLocation) {
uniformLocations[uniform] = uniformLocation;
}
}
}

this.fixedUniforms = fixedUniforms(context, uniformLocations);
this.binderUniforms = configuration ? configuration.getUniforms(context, uniformLocations) : [];
this.fixedUniforms = fixedUniforms(context);
this.binderUniforms = configuration ? configuration.getUniforms(context) : [];
if (fixedDefines.indexOf('TERRAIN') !== -1) {
this.terrainUniforms = terrainUniforms(context, uniformLocations);
this.terrainUniforms = terrainUniforms(context);
}
if (fixedDefines.indexOf('FOG') !== -1) {
this.fogUniforms = fogUniforms(context, uniformLocations);
this.fogUniforms = fogUniforms(context);
}
}

Expand All @@ -166,7 +157,7 @@ class Program<Us: UniformBindings> {
context.program.set(this.program);

for (const name in terrainUniformValues) {
uniforms[name].set(terrainUniformValues[name]);
uniforms[name].set(this.program, name, terrainUniformValues[name]);
}
}

Expand All @@ -178,9 +169,7 @@ class Program<Us: UniformBindings> {
context.program.set(this.program);

for (const name in fogUniformsValues) {
if (uniforms[name].location) {
uniforms[name].set(fogUniformsValues[name]);
}
uniforms[name].set(this.program, name, fogUniformsValues[name]);
}
}

Expand Down Expand Up @@ -212,11 +201,11 @@ class Program<Us: UniformBindings> {
context.setCullFace(cullFaceMode);

for (const name of Object.keys(this.fixedUniforms)) {
this.fixedUniforms[name].set(uniformValues[name]);
this.fixedUniforms[name].set(this.program, name, uniformValues[name]);
}

if (configuration) {
configuration.setUniforms(context, this.binderUniforms, currentProperties, {zoom: (zoom: any)});
configuration.setUniforms(this.program, context, this.binderUniforms, currentProperties, {zoom: (zoom: any)});
}

const primitiveSize = {
Expand Down
Loading