Skip to content

Commit e52f939

Browse files
stepankuzmingithub-actions[bot]
authored andcommitted
Optimize shader preprocessing and program creation (internal-7984)
GitOrigin-RevId: a0be904f3ba66e2a0c242ab326053829205924a1
1 parent 51b194a commit e52f939

File tree

7 files changed

+172
-352
lines changed

7 files changed

+172
-352
lines changed

build/rollup_plugins.js

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const plugins = ({mode, format, minified, production, test, bench, keepCl
5151
'process.env.UPDATE': JSON.stringify(process.env.UPDATE)
5252
}
5353
}) : false,
54-
glsl(['./src/shaders/*.glsl', './3d-style/shaders/*.glsl'], production),
54+
glsl(['./src/shaders/*.glsl', './3d-style/shaders/*.glsl']),
5555
minified ? terser({
5656
ecma: 2020,
5757
module: true,
@@ -72,30 +72,33 @@ export const plugins = ({mode, format, minified, production, test, bench, keepCl
7272
}),
7373
].filter(Boolean);
7474

75-
// Using this instead of rollup-plugin-string to add minification
76-
function glsl(include, minify) {
77-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
75+
/**
76+
* GLSL Shader Transform Plugin
77+
* Performs lightweight minification: strips comments, collapses whitespace, and removes unnecessary line breaks.
78+
* @param {string[]} include - Array of glob patterns to include
79+
* @returns {import('rollup').Plugin} - Rollup plugin object
80+
*/
81+
function glsl(include) {
7882
const filter = createFilter(include);
83+
84+
const COMMENT_REGEX = /\s*\/\/.*$/gm;
85+
const MULTILINE_REGEX = /\n+/g;
86+
const INDENT_REGEX = /\n\s+/g;
87+
const OPERATOR_REGEX = /\s?([+\-/*=,])\s?/g;
88+
const LINEBREAK_REGEX = /([;,{}])\n(?=[^#])/g;
89+
7990
return {
8091
name: 'glsl',
8192
transform(code, id) {
8293
if (!filter(id)) return;
8394

84-
// barebones GLSL minification
85-
if (minify) {
86-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
87-
code = code.trim() // strip whitespace at the start/end
88-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
89-
.replace(/\s*\/\/[^\n]*\n/g, '\n') // strip double-slash comments
90-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
91-
.replace(/\n+/g, '\n') // collapse multi line breaks
92-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
93-
.replace(/\n\s+/g, '\n') // strip indentation
94-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
95-
.replace(/\s?([+-\/*=,])\s?/g, '$1') // strip whitespace around operators
96-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
97-
.replace(/([;,\{\}])\n(?=[^#])/g, '$1'); // strip more line breaks
98-
}
95+
// GLSL minification
96+
code = code.trim() // strip whitespace at the start/end
97+
.replace(COMMENT_REGEX, '') // strip double-slash comments
98+
.replace(MULTILINE_REGEX, '\n') // collapse multi line breaks
99+
.replace(INDENT_REGEX, '\n') // strip indentation
100+
.replace(OPERATOR_REGEX, '$1') // strip whitespace around operators
101+
.replace(LINEBREAK_REGEX, '$1'); // strip more line breaks
99102

100103
return {
101104
code: `export default ${JSON.stringify(code)};`,

src/render/painter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1699,7 +1699,7 @@ class Painter {
16991699

17001700
getOrCreateProgram<T extends ProgramName>(name: T, options?: CreateProgramParams): Program<ProgramUniformsType[T]> {
17011701
this.cache = this.cache || {};
1702-
const defines = ((options && options.defines) || []);
1702+
const defines = (options && options.defines) || [];
17031703
const config = options && options.config;
17041704
const overrideFog = options && options.overrideFog;
17051705
const overrideRtt = options && options.overrideRtt;

src/render/program.ts

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import {
2-
prelude,
3-
preludeFragPrecisionQualifiers,
4-
preludeVertPrecisionQualifiers,
5-
preludeCommonSource,
62
includeMap,
7-
preludeFragExtensions,
3+
FRAGMENT_PRELUDE_BLOCK,
4+
VERTEX_PRELUDE_BLOCK
85
} from '../shaders/shaders';
96
import assert from 'assert';
107
import VertexArrayObject from './vertex_array_object';
@@ -30,7 +27,7 @@ import type SegmentVector from '../data/segment';
3027
import type VertexBuffer from '../gl/vertex_buffer';
3128
import type IndexBuffer from '../gl/index_buffer';
3229
import type CullFaceMode from '../gl/cull_face_mode';
33-
import type {UniformBindings, UniformValues} from './uniform_binding';
30+
import type {UniformBindings, UniformValues, IUniform} from './uniform_binding';
3431
import type {BinderUniform} from '../data/program_configuration';
3532
import type Painter from './painter';
3633
import type {Segment} from "../data/segment";
@@ -44,7 +41,7 @@ export type DrawMode = WebGL2RenderingContext['POINTS'] | WebGL2RenderingContext
4441
export type ShaderSource = {
4542
fragmentSource: string;
4643
vertexSource: string;
47-
usedDefines: Array<DynamicDefinesType>;
44+
usedDefines: Set<DynamicDefinesType>;
4845
vertexIncludes: Array<string>;
4946
fragmentIncludes: Array<string>;
5047
};
@@ -76,7 +73,9 @@ const instancingUniforms = (context: Context): InstancingUniformType => ({
7673
class Program<Us extends UniformBindings> {
7774
program: WebGLProgram;
7875
attributes: Record<string, number>;
79-
fixedUniforms: Us;
76+
fixedUniforms: Readonly<Us>;
77+
// Cache fixedUniforms entries to avoid allocations during draw calls
78+
fixedUniformsEntries: ReadonlyArray<[string, IUniform<unknown>]>;
8079
binderUniforms: Array<BinderUniform>;
8180
failedToCreate: boolean;
8281
terrainUniforms: TerrainUniformsType | null | undefined;
@@ -88,7 +87,7 @@ class Program<Us extends UniformBindings> {
8887

8988
name: ProgramName;
9089
configuration: ProgramConfiguration | null | undefined;
91-
fixedDefines: DynamicDefinesType[];
90+
fixedDefines: ReadonlyArray<DynamicDefinesType>;
9291

9392
// Manually handle instancing by issuing draw calls and replacing gl_InstanceID with uniform
9493
forceManualRenderingForInstanceIDShaders: boolean;
@@ -100,13 +99,19 @@ class Program<Us extends UniformBindings> {
10099
defines: DynamicDefinesType[],
101100
programConfiguration?: ProgramConfiguration | null,
102101
): string {
103-
let key = `${name}${programConfiguration ? programConfiguration.cacheKey : ''}`;
102+
const parts: string[] = [name];
103+
104+
if (programConfiguration) {
105+
parts.push(programConfiguration.cacheKey);
106+
}
107+
104108
for (const define of defines) {
105-
if (source.usedDefines.includes(define)) {
106-
key += `/${define}`;
109+
if (source.usedDefines.has(define)) {
110+
parts.push(define as string);
107111
}
108112
}
109-
return key;
113+
114+
return parts.join('/');
110115
}
111116

112117
constructor(
@@ -124,44 +129,36 @@ class Program<Us extends UniformBindings> {
124129
this.name = name;
125130
this.fixedDefines = [...fixedDefines];
126131

127-
let defines = configuration ? configuration.defines() : [];
128-
defines = defines.concat(fixedDefines.map((define) => `#define ${define}`));
129-
const version = '#version 300 es\n';
132+
const configDefines = configuration ? configuration.defines() : [];
133+
const defines = configDefines.concat(fixedDefines.map((define) => `#define ${define}`)).join('\n');
134+
const definesBlock = `#version 300 es\n${defines}`;
130135

131-
let fragmentSource = version + defines.concat(
132-
preludeFragExtensions,
133-
preludeFragPrecisionQualifiers,
134-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
135-
preludeCommonSource,
136-
prelude.fragmentSource).join('\n');
136+
const fragmentParts = [definesBlock, FRAGMENT_PRELUDE_BLOCK];
137137
for (const include of source.fragmentIncludes) {
138-
fragmentSource += `\n${includeMap[include]}`;
138+
fragmentParts.push(includeMap[include]);
139139
}
140-
fragmentSource += `\n${source.fragmentSource}`;
141140

142-
let vertexSource = version + defines.concat(
143-
preludeVertPrecisionQualifiers,
144-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
145-
preludeCommonSource,
146-
prelude.vertexSource).join('\n');
141+
fragmentParts.push(source.fragmentSource);
142+
const fragmentSource = fragmentParts.join('\n');
143+
144+
const vertexParts = [definesBlock, VERTEX_PRELUDE_BLOCK];
147145
for (const include of source.vertexIncludes) {
148-
vertexSource += `\n${includeMap[include]}`;
146+
vertexParts.push(includeMap[include]);
149147
}
150148

151-
this.forceManualRenderingForInstanceIDShaders = context.forceManualRenderingForInstanceIDShaders && source.vertexSource.indexOf("gl_InstanceID") !== -1;
149+
this.forceManualRenderingForInstanceIDShaders = context.forceManualRenderingForInstanceIDShaders && source.vertexSource.includes('gl_InstanceID');
152150

153151
if (this.forceManualRenderingForInstanceIDShaders) {
154-
vertexSource += `\nuniform int u_instanceID;\n`;
152+
vertexParts.push('uniform int u_instanceID;');
155153
}
156154

157-
vertexSource += `\n${source.vertexSource}`;
155+
vertexParts.push(source.vertexSource);
156+
let vertexSource = vertexParts.join('\n');
158157

159158
if (this.forceManualRenderingForInstanceIDShaders) {
160-
vertexSource = vertexSource.replaceAll("gl_InstanceID", "u_instanceID");
159+
vertexSource = vertexSource.replaceAll('gl_InstanceID', 'u_instanceID');
161160
}
162161

163-
// Replace
164-
165162
const fragmentShader = (gl.createShader(gl.FRAGMENT_SHADER));
166163
if (gl.isContextLost()) {
167164
this.failedToCreate = true;
@@ -191,6 +188,7 @@ class Program<Us extends UniformBindings> {
191188
gl.deleteShader(fragmentShader);
192189

193190
this.fixedUniforms = fixedUniforms(context);
191+
this.fixedUniformsEntries = Object.entries(this.fixedUniforms);
194192
this.binderUniforms = configuration ? configuration.getUniforms(context) : [];
195193

196194
if (this.forceManualRenderingForInstanceIDShaders) {
@@ -199,7 +197,7 @@ class Program<Us extends UniformBindings> {
199197

200198
// Symbol and circle layer are depth (terrain + 3d layers) occluded
201199
// For the sake of native compatibility depth occlusion goes via terrain uniforms block
202-
if (fixedDefines.includes('TERRAIN') || name.indexOf("symbol") !== -1 || name.indexOf("circle") !== -1) {
200+
if (fixedDefines.includes('TERRAIN') || name.includes('symbol') || name.includes('circle')) {
203201
this.terrainUniforms = terrainUniforms(context);
204202
}
205203
if (fixedDefines.includes('GLOBE')) {
@@ -363,17 +361,13 @@ class Program<Us extends UniformBindings> {
363361
return;
364362
}
365363

366-
const debugDefines: DynamicDefinesType[] = [...this.fixedDefines];
367-
debugDefines.push('DEBUG_WIREFRAME');
364+
const debugDefines: DynamicDefinesType[] = [...this.fixedDefines, 'DEBUG_WIREFRAME'];
368365
const debugProgram = painter.getOrCreateProgram(this.name, {config: this.configuration, defines: debugDefines});
369366

370367
context.program.set(debugProgram.program);
371368

372-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
373-
const copyUniformValues = (group: string, pSrc: any, pDst: any) => {
374-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
369+
const copyUniformValues = (group: string, pSrc: Program<Us>, pDst: Program<UniformBindings>) => {
375370
if (pSrc[group] && pDst[group]) {
376-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
377371
for (const name in pSrc[group]) {
378372
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
379373
if (pDst[group][name]) {
@@ -488,8 +482,8 @@ class Program<Us extends UniformBindings> {
488482
context.setColorMode(colorMode);
489483
context.setCullFace(cullFaceMode);
490484

491-
for (const name of Object.keys(this.fixedUniforms)) {
492-
this.fixedUniforms[name].set(this.program, name, uniformValues[name]);
485+
for (const [name, uniform] of this.fixedUniformsEntries) {
486+
uniform.set(this.program, name, uniformValues[name]);
493487
}
494488

495489
if (configuration) {
@@ -505,6 +499,10 @@ class Program<Us extends UniformBindings> {
505499

506500
this.checkUniforms(layerID, 'RENDER_SHADOWS', this.shadowUniforms);
507501

502+
const dynamicBuffers = dynamicLayoutBuffers || [];
503+
const paintVertexBuffers = configuration ? configuration.getPaintVertexBuffers() : [];
504+
const shouldDrawWireframe = drawMode === gl.TRIANGLES && indexBuffer;
505+
508506
const vertexAttribDivisorValue = instanceCount && instanceCount > 0 ? 1 : undefined;
509507
for (const segment of segments.get()) {
510508
const vaos = segment.vaos || (segment.vaos = {});
@@ -513,10 +511,10 @@ class Program<Us extends UniformBindings> {
513511
context,
514512
this,
515513
layoutVertexBuffer,
516-
configuration ? configuration.getPaintVertexBuffers() : [],
514+
paintVertexBuffers,
517515
indexBuffer,
518516
segment.vertexOffset,
519-
dynamicLayoutBuffers ? dynamicLayoutBuffers : [],
517+
dynamicBuffers,
520518
vertexAttribDivisorValue
521519
);
522520

@@ -555,8 +553,7 @@ class Program<Us extends UniformBindings> {
555553
gl.drawArrays(drawMode, segment.vertexOffset, segment.vertexLength);
556554
}
557555
}
558-
if (drawMode === gl.TRIANGLES && indexBuffer) {
559-
// Handle potential wireframe rendering for current draw call
556+
if (shouldDrawWireframe) {
560557
this._drawDebugWireframe(painter, depthMode, stencilMode, colorMode, indexBuffer, segment,
561558
currentProperties, zoom, configuration, instanceCount);
562559
}

src/render/uniform_binding.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,4 @@ export {
210210
};
211211

212212
// eslint-disable-next-line @typescript-eslint/no-explicit-any
213-
export type UniformBindings = Record<string, IUniform<any>>;
213+
export type UniformBindings = Record<PropertyKey, IUniform<any>>;

0 commit comments

Comments
 (0)