From be56ece73e9d5da19ed1ee80462a95ab25380b76 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Thu, 12 Dec 2024 22:00:59 +0300 Subject: [PATCH 01/12] refactor: WebGL2 to WebGL1 fallback at runtime --- build/generate-shaders.ts | 56 +++++++++------------------------------ src/render/program.ts | 23 ++++++++++++++-- 2 files changed, 33 insertions(+), 46 deletions(-) diff --git a/build/generate-shaders.ts b/build/generate-shaders.ts index 5f3e2409d7..9e652dbe4d 100644 --- a/build/generate-shaders.ts +++ b/build/generate-shaders.ts @@ -12,51 +12,9 @@ console.log('Generating shaders'); * It will also create a simple package.json file to allow importing this package in webpack */ -const vertex = globSync('./src/shaders/*.vertex.glsl'); -for (const file of vertex) { - const code = fs.readFileSync(file, 'utf8'); - const content = glslToTs(code, 'vertex'); - const fileName = path.join('.', 'src', 'shaders', `${file.split(path.sep).splice(-1)}.g.ts`); - fs.writeFileSync(fileName, content); -} - -console.log(`Finished converting ${vertex.length} vertex shaders`); - -const fragment = globSync('./src/shaders/*.fragment.glsl'); -for (const file of fragment) { - const code = fs.readFileSync(file, 'utf8'); - const content = glslToTs(code, 'fragment'); - const fileName = path.join('.', 'src', 'shaders', `${file.split(path.sep).splice(-1)}.g.ts`); - fs.writeFileSync(fileName, content); -} - -console.log(`Finished converting ${fragment.length} fragment shaders`); - -function glslToTs(code: string, type: 'fragment'|'vertex'): string { - code = code - .trim(); // strip whitespace at the start/end - - // WebGL1 Compat -- Start - - if (type === 'fragment') { - code = code - .replace(/\bin\s/g, 'varying ') // For fragment shaders, replace "in " with "varying " - .replace('out highp vec4 fragColor;', ''); - } - - if (type === 'vertex') { - code = code - .replace(/\bin\s/g, 'attribute ') // For vertex shaders, replace "in " with "attribute " - .replace(/\bout\s/g, 'varying '); // For vertex shaders, replace "out " with "varying " - } - - code = code - .replace(/fragColor/g, 'gl_FragColor') - .replace(/texture\(/g, 'texture2D('); - - // WebGL1 Compat -- End - +function glslToTs(code: string): string { code = code + .trim() // strip whitespace at the start/end .replace(/\s*\/\/[^\n]*\n/g, '\n') // strip double-slash comments .replace(/\n+/g, '\n') // collapse multi line breaks .replace(/\n\s+/g, '\n') // strip indentation @@ -66,3 +24,13 @@ function glslToTs(code: string, type: 'fragment'|'vertex'): string { return `// This file is generated. Edit build/generate-shaders.ts, then run \`npm run codegen\`. export default ${JSON.stringify(code).replaceAll('"', '\'')};\n`; } + +const shaderFiles = globSync('./src/shaders/*.glsl'); +for (const file of shaderFiles) { + const glslFile = fs.readFileSync(file, 'utf8'); + const tsSource = glslToTs(glslFile); + const fileName = path.join('.', 'src', 'shaders', `${file.split(path.sep).splice(-1)}.g.ts`); + fs.writeFileSync(fileName, tsSource); +} + +console.log(`Finished converting ${shaderFiles.length} shaders`); diff --git a/src/render/program.ts b/src/render/program.ts index 78110d0344..c61c26ce78 100644 --- a/src/render/program.ts +++ b/src/render/program.ts @@ -54,6 +54,7 @@ export class Program { projectionDefine: string) { const gl = context.gl; + const isWebGL2 = gl instanceof WebGL2RenderingContext; this.program = gl.createProgram(); const staticAttrInfo = getTokenizedAttributesAndUniforms(source.staticAttributes); @@ -72,6 +73,9 @@ export class Program { } const defines = configuration ? configuration.defines() : []; + if (isWebGL2) { + defines.unshift('#version 300 es'); + } if (showOverdrawInspector) { defines.push('#define OVERDRAW_INSPECTOR;'); } @@ -82,8 +86,23 @@ export class Program { defines.push(projectionDefine); } - const fragmentSource = defines.concat(shaders.prelude.fragmentSource, projectionPrelude.fragmentSource, source.fragmentSource).join('\n'); - const vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join('\n'); + let fragmentSource = defines.concat(shaders.prelude.fragmentSource, projectionPrelude.fragmentSource, source.fragmentSource).join('\n'); + let vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join('\n'); + + if (!isWebGL2) { + // WebGL1 Compatibility + // Convert WebGL2 shader sources to WebGL1 + fragmentSource = fragmentSource + .replace(/\bin\s/g, 'varying ') + .replace('out highp vec4 fragColor;', '') + .replace(/fragColor/g, 'gl_FragColor') + .replace(/texture\(/g, 'texture2D('); + vertexSource = vertexSource + .replace(/\bin\s/g, 'attribute ') + .replace(/\bout\s/g, 'varying ') + .replace(/texture\(/g, 'texture2D('); + // ~WebGL1 Compatibility + } const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); if (gl.isContextLost()) { From 821cf47eaf2277c7dddfac8932cf48a052c862ab Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Thu, 12 Dec 2024 22:05:28 +0300 Subject: [PATCH 02/12] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0602ba242c..d53fa44e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ ## main ### ✨ Features and improvements + - Add support for projection type expression as part of a refactoring of the transfrom and projection classes ([#5139](https://github.com/maplibre/maplibre-gl-js/pull/5139)) +- Add WebGL2 to WebGL1 fallback at runtime ([#5198](https://github.com/maplibre/maplibre-gl-js/pull/5198)) - _...Add new stuff here..._ ### 🐞 Bug fixes + - Fix globe custom layers being supplied incorrect matrices after projection transition to mercator ([#5150](https://github.com/maplibre/maplibre-gl-js/pull/5150)) - Fix custom 3D models disappearing during projection transition ([#5150](https://github.com/maplibre/maplibre-gl-js/pull/5150)) - _...Add new stuff here..._ From 711a46be47f93d679069b248040043e98330a7e4 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Thu, 12 Dec 2024 22:15:12 +0300 Subject: [PATCH 03/12] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d53fa44e2b..867b6157b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### ✨ Features and improvements - Add support for projection type expression as part of a refactoring of the transfrom and projection classes ([#5139](https://github.com/maplibre/maplibre-gl-js/pull/5139)) -- Add WebGL2 to WebGL1 fallback at runtime ([#5198](https://github.com/maplibre/maplibre-gl-js/pull/5198)) +- Add Dual-Stack WebGL Runtime with WebGL2 to WebGL1 Fallback ([#5198](https://github.com/maplibre/maplibre-gl-js/pull/5198)) - _...Add new stuff here..._ ### 🐞 Bug fixes From ef2e836bf14750b2a22831a08394a2cbcd60634a Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Thu, 12 Dec 2024 22:25:05 +0300 Subject: [PATCH 04/12] replace isWebGL2 check --- src/render/program.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/render/program.ts b/src/render/program.ts index c61c26ce78..7ca03001da 100644 --- a/src/render/program.ts +++ b/src/render/program.ts @@ -2,6 +2,7 @@ import {type PreparedShader, shaders} from '../shaders/shaders'; import {type ProgramConfiguration} from '../data/program_configuration'; import {VertexArrayObject} from './vertex_array_object'; import {type Context} from '../gl/context'; +import {isWebGL2} from '../gl/webgl2'; import type {SegmentVector} from '../data/segment'; import type {VertexBuffer} from '../gl/vertex_buffer'; @@ -54,7 +55,6 @@ export class Program { projectionDefine: string) { const gl = context.gl; - const isWebGL2 = gl instanceof WebGL2RenderingContext; this.program = gl.createProgram(); const staticAttrInfo = getTokenizedAttributesAndUniforms(source.staticAttributes); @@ -73,7 +73,7 @@ export class Program { } const defines = configuration ? configuration.defines() : []; - if (isWebGL2) { + if (isWebGL2(gl)) { defines.unshift('#version 300 es'); } if (showOverdrawInspector) { @@ -89,7 +89,7 @@ export class Program { let fragmentSource = defines.concat(shaders.prelude.fragmentSource, projectionPrelude.fragmentSource, source.fragmentSource).join('\n'); let vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join('\n'); - if (!isWebGL2) { + if (!isWebGL2(gl)) { // WebGL1 Compatibility // Convert WebGL2 shader sources to WebGL1 fragmentSource = fragmentSource From ebf34811a23660276c3337c4c737924514ae2847 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Fri, 13 Dec 2024 12:57:38 +0300 Subject: [PATCH 05/12] revert program.ts --- src/render/program.ts | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/render/program.ts b/src/render/program.ts index 7ca03001da..78110d0344 100644 --- a/src/render/program.ts +++ b/src/render/program.ts @@ -2,7 +2,6 @@ import {type PreparedShader, shaders} from '../shaders/shaders'; import {type ProgramConfiguration} from '../data/program_configuration'; import {VertexArrayObject} from './vertex_array_object'; import {type Context} from '../gl/context'; -import {isWebGL2} from '../gl/webgl2'; import type {SegmentVector} from '../data/segment'; import type {VertexBuffer} from '../gl/vertex_buffer'; @@ -73,9 +72,6 @@ export class Program { } const defines = configuration ? configuration.defines() : []; - if (isWebGL2(gl)) { - defines.unshift('#version 300 es'); - } if (showOverdrawInspector) { defines.push('#define OVERDRAW_INSPECTOR;'); } @@ -86,23 +82,8 @@ export class Program { defines.push(projectionDefine); } - let fragmentSource = defines.concat(shaders.prelude.fragmentSource, projectionPrelude.fragmentSource, source.fragmentSource).join('\n'); - let vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join('\n'); - - if (!isWebGL2(gl)) { - // WebGL1 Compatibility - // Convert WebGL2 shader sources to WebGL1 - fragmentSource = fragmentSource - .replace(/\bin\s/g, 'varying ') - .replace('out highp vec4 fragColor;', '') - .replace(/fragColor/g, 'gl_FragColor') - .replace(/texture\(/g, 'texture2D('); - vertexSource = vertexSource - .replace(/\bin\s/g, 'attribute ') - .replace(/\bout\s/g, 'varying ') - .replace(/texture\(/g, 'texture2D('); - // ~WebGL1 Compatibility - } + const fragmentSource = defines.concat(shaders.prelude.fragmentSource, projectionPrelude.fragmentSource, source.fragmentSource).join('\n'); + const vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join('\n'); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); if (gl.isContextLost()) { From 1dfe22733ae583a8feb314671725253528f06459 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Fri, 13 Dec 2024 14:54:31 +0300 Subject: [PATCH 06/12] implement shaders.transpileToWebGL1(), refactor shaders.prepare() --- src/render/program.ts | 15 ++++-- src/shaders/shaders.ts | 108 +++++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 49 deletions(-) diff --git a/src/render/program.ts b/src/render/program.ts index 78110d0344..9f376b3696 100644 --- a/src/render/program.ts +++ b/src/render/program.ts @@ -1,7 +1,8 @@ -import {type PreparedShader, shaders} from '../shaders/shaders'; +import {type PreparedShader, shaders, transpileToWebGL1} from '../shaders/shaders'; import {type ProgramConfiguration} from '../data/program_configuration'; import {VertexArrayObject} from './vertex_array_object'; import {type Context} from '../gl/context'; +import {isWebGL2} from '../gl/webgl2'; import type {SegmentVector} from '../data/segment'; import type {VertexBuffer} from '../gl/vertex_buffer'; @@ -72,6 +73,9 @@ export class Program { } const defines = configuration ? configuration.defines() : []; + if (isWebGL2(gl)) { + defines.unshift('#version 300 es'); + } if (showOverdrawInspector) { defines.push('#define OVERDRAW_INSPECTOR;'); } @@ -82,8 +86,13 @@ export class Program { defines.push(projectionDefine); } - const fragmentSource = defines.concat(shaders.prelude.fragmentSource, projectionPrelude.fragmentSource, source.fragmentSource).join('\n'); - const vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join('\n'); + let fragmentSource = defines.concat(shaders.prelude.fragmentSource, projectionPrelude.fragmentSource, source.fragmentSource).join('\n'); + let vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join('\n'); + + if (!isWebGL2(gl)) { + fragmentSource = transpileToWebGL1('fragment', fragmentSource); + vertexSource = transpileToWebGL1('vertex', vertexSource); + } const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); if (gl.isContextLost()) { diff --git a/src/shaders/shaders.ts b/src/shaders/shaders.ts index bc7b88f2ec..aced0b9302 100644 --- a/src/shaders/shaders.ts +++ b/src/shaders/shaders.ts @@ -1,4 +1,3 @@ - // Disable Flow annotations here because Flow doesn't support importing GLSL files import preludeFrag from './_prelude.fragment.glsl.g'; @@ -77,52 +76,51 @@ export type PreparedShader = { }; export const shaders = { - prelude: compile(preludeFrag, preludeVert), - projectionMercator: compile('', projectionMercatorVert), - projectionGlobe: compile('', projectionGlobeVert), - background: compile(backgroundFrag, backgroundVert), - backgroundPattern: compile(backgroundPatternFrag, backgroundPatternVert), - circle: compile(circleFrag, circleVert), - clippingMask: compile(clippingMaskFrag, clippingMaskVert), - heatmap: compile(heatmapFrag, heatmapVert), - heatmapTexture: compile(heatmapTextureFrag, heatmapTextureVert), - collisionBox: compile(collisionBoxFrag, collisionBoxVert), - collisionCircle: compile(collisionCircleFrag, collisionCircleVert), - debug: compile(debugFrag, debugVert), - depth: compile(clippingMaskFrag, depthVert), - fill: compile(fillFrag, fillVert), - fillOutline: compile(fillOutlineFrag, fillOutlineVert), - fillOutlinePattern: compile(fillOutlinePatternFrag, fillOutlinePatternVert), - fillPattern: compile(fillPatternFrag, fillPatternVert), - fillExtrusion: compile(fillExtrusionFrag, fillExtrusionVert), - fillExtrusionPattern: compile(fillExtrusionPatternFrag, fillExtrusionPatternVert), - hillshadePrepare: compile(hillshadePrepareFrag, hillshadePrepareVert), - hillshade: compile(hillshadeFrag, hillshadeVert), - line: compile(lineFrag, lineVert), - lineGradient: compile(lineGradientFrag, lineGradientVert), - linePattern: compile(linePatternFrag, linePatternVert), - lineSDF: compile(lineSDFFrag, lineSDFVert), - raster: compile(rasterFrag, rasterVert), - symbolIcon: compile(symbolIconFrag, symbolIconVert), - symbolSDF: compile(symbolSDFFrag, symbolSDFVert), - symbolTextAndIcon: compile(symbolTextAndIconFrag, symbolTextAndIconVert), - terrain: compile(terrainFrag, terrainVert), - terrainDepth: compile(terrainDepthFrag, terrainVertDepth), - terrainCoords: compile(terrainCoordsFrag, terrainVertCoords), - projectionErrorMeasurement: compile(projectionErrorMeasurementFrag, projectionErrorMeasurementVert), - atmosphere: compile(atmosphereFrag, atmosphereVert), - sky: compile(skyFrag, skyVert), + prelude: prepare(preludeFrag, preludeVert), + projectionMercator: prepare('', projectionMercatorVert), + projectionGlobe: prepare('', projectionGlobeVert), + background: prepare(backgroundFrag, backgroundVert), + backgroundPattern: prepare(backgroundPatternFrag, backgroundPatternVert), + circle: prepare(circleFrag, circleVert), + clippingMask: prepare(clippingMaskFrag, clippingMaskVert), + heatmap: prepare(heatmapFrag, heatmapVert), + heatmapTexture: prepare(heatmapTextureFrag, heatmapTextureVert), + collisionBox: prepare(collisionBoxFrag, collisionBoxVert), + collisionCircle: prepare(collisionCircleFrag, collisionCircleVert), + debug: prepare(debugFrag, debugVert), + depth: prepare(clippingMaskFrag, depthVert), + fill: prepare(fillFrag, fillVert), + fillOutline: prepare(fillOutlineFrag, fillOutlineVert), + fillOutlinePattern: prepare(fillOutlinePatternFrag, fillOutlinePatternVert), + fillPattern: prepare(fillPatternFrag, fillPatternVert), + fillExtrusion: prepare(fillExtrusionFrag, fillExtrusionVert), + fillExtrusionPattern: prepare(fillExtrusionPatternFrag, fillExtrusionPatternVert), + hillshadePrepare: prepare(hillshadePrepareFrag, hillshadePrepareVert), + hillshade: prepare(hillshadeFrag, hillshadeVert), + line: prepare(lineFrag, lineVert), + lineGradient: prepare(lineGradientFrag, lineGradientVert), + linePattern: prepare(linePatternFrag, linePatternVert), + lineSDF: prepare(lineSDFFrag, lineSDFVert), + raster: prepare(rasterFrag, rasterVert), + symbolIcon: prepare(symbolIconFrag, symbolIconVert), + symbolSDF: prepare(symbolSDFFrag, symbolSDFVert), + symbolTextAndIcon: prepare(symbolTextAndIconFrag, symbolTextAndIconVert), + terrain: prepare(terrainFrag, terrainVert), + terrainDepth: prepare(terrainDepthFrag, terrainVertDepth), + terrainCoords: prepare(terrainCoordsFrag, terrainVertCoords), + projectionErrorMeasurement: prepare(projectionErrorMeasurementFrag, projectionErrorMeasurementVert), + atmosphere: prepare(atmosphereFrag, atmosphereVert), + sky: prepare(skyFrag, skyVert), }; -// Expand #pragmas to #ifdefs. - -function compile(fragmentSource: string, vertexSource: string): PreparedShader { +/** Expand #pragmas to #ifdefs, extract attributes and uniforms */ +function prepare(fragmentSource: string, vertexSource: string): PreparedShader { const re = /#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g; - const staticAttributes = vertexSource.match(/attribute ([\w]+) ([\w]+)/g); + const vertexAttributes = vertexSource.match(/in ([\w]+) ([\w]+)/g); const fragmentUniforms = fragmentSource.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g); const vertexUniforms = vertexSource.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g); - const staticUniforms = vertexUniforms ? vertexUniforms.concat(fragmentUniforms) : fragmentUniforms; + const shaderUniforms = vertexUniforms ? vertexUniforms.concat(fragmentUniforms) : fragmentUniforms; const fragmentPragmas = {}; @@ -131,7 +129,7 @@ function compile(fragmentSource: string, vertexSource: string): PreparedShader { if (operation === 'define') { return ` #ifndef HAS_UNIFORM_u_${name} -varying ${precision} ${type} ${name}; +in ${precision} ${type} ${name}; #else uniform ${precision} ${type} u_${name}; #endif @@ -154,8 +152,8 @@ uniform ${precision} ${type} u_${name}; return ` #ifndef HAS_UNIFORM_u_${name} uniform lowp float u_${name}_t; -attribute ${precision} ${attrType} a_${name}; -varying ${precision} ${type} ${name}; +in ${precision} ${attrType} a_${name}; +out ${precision} ${type} ${name}; #else uniform ${precision} ${type} u_${name}; #endif @@ -185,7 +183,7 @@ uniform ${precision} ${type} u_${name}; return ` #ifndef HAS_UNIFORM_u_${name} uniform lowp float u_${name}_t; -attribute ${precision} ${attrType} a_${name}; +in ${precision} ${attrType} a_${name}; #else uniform ${precision} ${type} u_${name}; #endif @@ -213,5 +211,23 @@ uniform ${precision} ${type} u_${name}; } }); - return {fragmentSource, vertexSource, staticAttributes, staticUniforms}; + return {fragmentSource, vertexSource, staticAttributes: vertexAttributes, staticUniforms: shaderUniforms}; +} + +/** Transpile WebGL2 shader source to WebGL1 */ +export function transpileToWebGL1(type: 'fragment' | 'vertex', src: string): string { + if (type === 'fragment') { + return src + .replace(/\bin\s/g, 'varying ') + .replace('out highp vec4 fragColor;', '') + .replace(/fragColor/g, 'gl_FragColor') + .replace(/texture\(/g, 'texture2D('); + } + if (type === 'vertex') { + return src + .replace(/\bin\s/g, 'attribute ') + .replace(/\bout\s/g, 'varying ') + .replace(/texture\(/g, 'texture2D('); + } + throw new Error(`Unexpected shader type: ${type}`); } From 75e65b2910c78576887130d62112fac98f2c0563 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Fri, 13 Dec 2024 15:14:29 +0300 Subject: [PATCH 07/12] revert readme --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 867b6157b3..0602ba242c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,10 @@ ## main ### ✨ Features and improvements - - Add support for projection type expression as part of a refactoring of the transfrom and projection classes ([#5139](https://github.com/maplibre/maplibre-gl-js/pull/5139)) -- Add Dual-Stack WebGL Runtime with WebGL2 to WebGL1 Fallback ([#5198](https://github.com/maplibre/maplibre-gl-js/pull/5198)) - _...Add new stuff here..._ ### 🐞 Bug fixes - - Fix globe custom layers being supplied incorrect matrices after projection transition to mercator ([#5150](https://github.com/maplibre/maplibre-gl-js/pull/5150)) - Fix custom 3D models disappearing during projection transition ([#5150](https://github.com/maplibre/maplibre-gl-js/pull/5150)) - _...Add new stuff here..._ From bc51178cc44873005c47855e143c714226c49728 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Fri, 13 Dec 2024 16:16:33 +0300 Subject: [PATCH 08/12] add `shaders.test.ts` --- src/shaders/shaders.test.ts | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/shaders/shaders.test.ts diff --git a/src/shaders/shaders.test.ts b/src/shaders/shaders.test.ts new file mode 100644 index 0000000000..a533773778 --- /dev/null +++ b/src/shaders/shaders.test.ts @@ -0,0 +1,40 @@ +import {describe, test, expect} from 'vitest'; +import {transpileToWebGL1} from './shaders'; + +describe('Shaders', () => { + test('webgl2 to webgl1 transpiled shader should be identical', () => { + const vertexSourceWebGL2 = ` + in vec3 aPos; + uniform mat4 u_matrix; + void main() { + gl_Position = u_matrix * vec4(aPos, 1.0); + gl_PointSize = 20.0; + }`; + const fragmentSourceWebGL2 = ` + out highp vec4 fragColor; + void main() { + fragColor = vec4(1.0, 0.0, 0.0, 1.0); + }`; + const vertexSourceWebGL1 = ` + attribute vec3 aPos; + uniform mat4 u_matrix; + void main() { + gl_Position = u_matrix * vec4(aPos, 1.0); + gl_PointSize = 20.0; + }`; + const fragmentSourceWebGL1 = ` + void main() { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + }`; + const vertexSourceTranspiled = transpileToWebGL1('vertex', vertexSourceWebGL2); + const fragmentSourceTranspiled = transpileToWebGL1('fragment', fragmentSourceWebGL2); + expect(vertexSourceTranspiled.trim()).equals(vertexSourceWebGL1.trim()); + expect(fragmentSourceTranspiled.trim()).equals(fragmentSourceWebGL1.trim()); + }); + + test('built-in shaders should be written in WebGL2', () => { + }); + + test('built-in shaders should be able to transpiled to WebGL1', () => { + }); +}); From 9ca21fc3a66912cf6e9d88dbb22667fd7ad45b67 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Fri, 13 Dec 2024 17:10:02 +0300 Subject: [PATCH 09/12] more shader tests --- src/shaders/shaders.test.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/shaders/shaders.test.ts b/src/shaders/shaders.test.ts index a533773778..c49af99892 100644 --- a/src/shaders/shaders.test.ts +++ b/src/shaders/shaders.test.ts @@ -1,7 +1,14 @@ import {describe, test, expect} from 'vitest'; import {transpileToWebGL1} from './shaders'; +import {globSync} from 'glob'; +import fs from 'fs'; describe('Shaders', () => { + test('`transpileToWebGL1()` should throw for unexpected type', () => { + //@ts-expect-error + expect(transpileToWebGL1('invalid', '')).toThrowError(); + }); + test('webgl2 to webgl1 transpiled shader should be identical', () => { const vertexSourceWebGL2 = ` in vec3 aPos; @@ -32,9 +39,30 @@ describe('Shaders', () => { expect(fragmentSourceTranspiled.trim()).equals(fragmentSourceWebGL1.trim()); }); + // reference: https://webgl2fundamentals.org/webgl/lessons/webgl1-to-webgl2.html test('built-in shaders should be written in WebGL2', () => { + const shaderFiles = globSync('./*.glsl'); + for (const shaderFile of shaderFiles) { + const shaderSource = fs.readFileSync(shaderFile, 'utf8'); + expect(shaderSource.includes('attribute')).toBe(false); + expect(shaderSource.includes('varying')).toBe(false); + expect(shaderSource.includes('gl_FragColor')).toBe(false); + expect(shaderSource.includes('texture2D')).toBe(false); + } }); - test('built-in shaders should be able to transpiled to WebGL1', () => { + test('built-in shaders should be able to `transpileToWebGL1()`', () => { + const vertexShaderFiles = globSync('./*.vertex.glsl'); + for (const vertexShaderFile of vertexShaderFiles) { + const vertexShaderSource = fs.readFileSync(vertexShaderFile, 'utf8'); + const vertexShaderTranspiled = transpileToWebGL1('vertex', vertexShaderSource); + expect(vertexShaderSource.trim()).not.equals(vertexShaderTranspiled.trim()); + } + const fragmentShaderFiles = globSync('./*.fragment.glsl'); + for (const fragmentShaderFile of fragmentShaderFiles) { + const fragmentShaderSource = fs.readFileSync(fragmentShaderFile, 'utf8'); + const fragmentShaderTranspiled = transpileToWebGL1('fragment', fragmentShaderSource); + expect(fragmentShaderSource.trim()).not.equals(fragmentShaderTranspiled.trim()); + } }); }); From f3d991c53573ce0e7516ade91765eb33e97f4bca Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Fri, 13 Dec 2024 17:10:56 +0300 Subject: [PATCH 10/12] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e89b7edb..6e3ba7bb72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ ## main ### ✨ Features and improvements + - Add support for projection type expression as part of a refactoring of the transfrom and projection classes ([#5139](https://github.com/maplibre/maplibre-gl-js/pull/5139)) - ⚠️ Support setting WebGL context options on map creation ([#5196](https://github.com/maplibre/maplibre-gl-js/pull/5196)). Previously supported WebGL context options like `antialias`, `preserveDrawingBuffer` and `failIfMajorPerformanceCaveat` must now be defined inside the `canvasContextAttributes` object on `MapOptions`. +- Add Dual-Stack WebGL Runtime with WebGL2 to WebGL1 Fallback ([#5198](https://github.com/maplibre/maplibre-gl-js/pull/5198)) - _...Add new stuff here..._ ### 🐞 Bug fixes + - Fix globe custom layers being supplied incorrect matrices after projection transition to mercator ([#5150](https://github.com/maplibre/maplibre-gl-js/pull/5150)) - Fix custom 3D models disappearing during projection transition ([#5150](https://github.com/maplibre/maplibre-gl-js/pull/5150)) - _...Add new stuff here..._ From f59c420a13bb22f165887ddd6ac736642af60a93 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Fri, 13 Dec 2024 17:24:36 +0300 Subject: [PATCH 11/12] fix shader transpileToWebGL1 throw test --- src/shaders/shaders.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shaders/shaders.test.ts b/src/shaders/shaders.test.ts index c49af99892..bd01739de6 100644 --- a/src/shaders/shaders.test.ts +++ b/src/shaders/shaders.test.ts @@ -6,7 +6,7 @@ import fs from 'fs'; describe('Shaders', () => { test('`transpileToWebGL1()` should throw for unexpected type', () => { //@ts-expect-error - expect(transpileToWebGL1('invalid', '')).toThrowError(); + expect(() => transpileToWebGL1('invalid', '')).toThrow(); }); test('webgl2 to webgl1 transpiled shader should be identical', () => { From 09266ce8761181bf274f08c68b8613e7edb91cb5 Mon Sep 17 00:00:00 2001 From: Fatih Mar Date: Mon, 16 Dec 2024 02:47:16 +0300 Subject: [PATCH 12/12] addressed code review feedback --- CHANGELOG.md | 2 +- src/render/program.ts | 6 ++-- src/shaders/shaders.ts | 31 ++++++++++----------- {src/shaders => test/build}/shaders.test.ts | 30 ++++---------------- 4 files changed, 24 insertions(+), 45 deletions(-) rename {src/shaders => test/build}/shaders.test.ts (53%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e3ba7bb72..b7a46b1aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Add support for projection type expression as part of a refactoring of the transfrom and projection classes ([#5139](https://github.com/maplibre/maplibre-gl-js/pull/5139)) - ⚠️ Support setting WebGL context options on map creation ([#5196](https://github.com/maplibre/maplibre-gl-js/pull/5196)). Previously supported WebGL context options like `antialias`, `preserveDrawingBuffer` and `failIfMajorPerformanceCaveat` must now be defined inside the `canvasContextAttributes` object on `MapOptions`. -- Add Dual-Stack WebGL Runtime with WebGL2 to WebGL1 Fallback ([#5198](https://github.com/maplibre/maplibre-gl-js/pull/5198)) +- Dual-Stack WebGL Runtime with WebGL2 to WebGL1 Fallback ([#5198](https://github.com/maplibre/maplibre-gl-js/pull/5198)) - _...Add new stuff here..._ ### 🐞 Bug fixes diff --git a/src/render/program.ts b/src/render/program.ts index 9f376b3696..efdbe51c45 100644 --- a/src/render/program.ts +++ b/src/render/program.ts @@ -1,4 +1,4 @@ -import {type PreparedShader, shaders, transpileToWebGL1} from '../shaders/shaders'; +import {type PreparedShader, shaders, transpileVertexShaderToWebGL1, transpileFragmentShaderToWebGL1} from '../shaders/shaders'; import {type ProgramConfiguration} from '../data/program_configuration'; import {VertexArrayObject} from './vertex_array_object'; import {type Context} from '../gl/context'; @@ -90,8 +90,8 @@ export class Program { let vertexSource = defines.concat(shaders.prelude.vertexSource, projectionPrelude.vertexSource, source.vertexSource).join('\n'); if (!isWebGL2(gl)) { - fragmentSource = transpileToWebGL1('fragment', fragmentSource); - vertexSource = transpileToWebGL1('vertex', vertexSource); + fragmentSource = transpileFragmentShaderToWebGL1(fragmentSource); + vertexSource = transpileVertexShaderToWebGL1(vertexSource); } const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); diff --git a/src/shaders/shaders.ts b/src/shaders/shaders.ts index aced0b9302..d8ad62f0d3 100644 --- a/src/shaders/shaders.ts +++ b/src/shaders/shaders.ts @@ -214,20 +214,19 @@ uniform ${precision} ${type} u_${name}; return {fragmentSource, vertexSource, staticAttributes: vertexAttributes, staticUniforms: shaderUniforms}; } -/** Transpile WebGL2 shader source to WebGL1 */ -export function transpileToWebGL1(type: 'fragment' | 'vertex', src: string): string { - if (type === 'fragment') { - return src - .replace(/\bin\s/g, 'varying ') - .replace('out highp vec4 fragColor;', '') - .replace(/fragColor/g, 'gl_FragColor') - .replace(/texture\(/g, 'texture2D('); - } - if (type === 'vertex') { - return src - .replace(/\bin\s/g, 'attribute ') - .replace(/\bout\s/g, 'varying ') - .replace(/texture\(/g, 'texture2D('); - } - throw new Error(`Unexpected shader type: ${type}`); +/** Transpile WebGL2 vertex shader source to WebGL1 */ +export function transpileVertexShaderToWebGL1(source: string): string { + return source + .replace(/\bin\s/g, 'attribute ') + .replace(/\bout\s/g, 'varying ') + .replace(/texture\(/g, 'texture2D('); +} + +/** Transpile WebGL2 fragment shader source to WebGL1 */ +export function transpileFragmentShaderToWebGL1(source: string): string { + return source + .replace(/\bin\s/g, 'varying ') + .replace('out highp vec4 fragColor;', '') + .replace(/fragColor/g, 'gl_FragColor') + .replace(/texture\(/g, 'texture2D('); } diff --git a/src/shaders/shaders.test.ts b/test/build/shaders.test.ts similarity index 53% rename from src/shaders/shaders.test.ts rename to test/build/shaders.test.ts index bd01739de6..3bf16ff5ae 100644 --- a/src/shaders/shaders.test.ts +++ b/test/build/shaders.test.ts @@ -1,15 +1,10 @@ +import {transpileVertexShaderToWebGL1, transpileFragmentShaderToWebGL1} from '../../src/shaders/shaders'; import {describe, test, expect} from 'vitest'; -import {transpileToWebGL1} from './shaders'; import {globSync} from 'glob'; import fs from 'fs'; describe('Shaders', () => { - test('`transpileToWebGL1()` should throw for unexpected type', () => { - //@ts-expect-error - expect(() => transpileToWebGL1('invalid', '')).toThrow(); - }); - - test('webgl2 to webgl1 transpiled shader should be identical', () => { + test('webgl2 to webgl1 transpiled shaders should be identical', () => { const vertexSourceWebGL2 = ` in vec3 aPos; uniform mat4 u_matrix; @@ -33,15 +28,15 @@ describe('Shaders', () => { void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }`; - const vertexSourceTranspiled = transpileToWebGL1('vertex', vertexSourceWebGL2); - const fragmentSourceTranspiled = transpileToWebGL1('fragment', fragmentSourceWebGL2); + const vertexSourceTranspiled = transpileVertexShaderToWebGL1(vertexSourceWebGL2); + const fragmentSourceTranspiled = transpileFragmentShaderToWebGL1(fragmentSourceWebGL2); expect(vertexSourceTranspiled.trim()).equals(vertexSourceWebGL1.trim()); expect(fragmentSourceTranspiled.trim()).equals(fragmentSourceWebGL1.trim()); }); // reference: https://webgl2fundamentals.org/webgl/lessons/webgl1-to-webgl2.html test('built-in shaders should be written in WebGL2', () => { - const shaderFiles = globSync('./*.glsl'); + const shaderFiles = globSync('../../src/shaders/*.glsl'); for (const shaderFile of shaderFiles) { const shaderSource = fs.readFileSync(shaderFile, 'utf8'); expect(shaderSource.includes('attribute')).toBe(false); @@ -50,19 +45,4 @@ describe('Shaders', () => { expect(shaderSource.includes('texture2D')).toBe(false); } }); - - test('built-in shaders should be able to `transpileToWebGL1()`', () => { - const vertexShaderFiles = globSync('./*.vertex.glsl'); - for (const vertexShaderFile of vertexShaderFiles) { - const vertexShaderSource = fs.readFileSync(vertexShaderFile, 'utf8'); - const vertexShaderTranspiled = transpileToWebGL1('vertex', vertexShaderSource); - expect(vertexShaderSource.trim()).not.equals(vertexShaderTranspiled.trim()); - } - const fragmentShaderFiles = globSync('./*.fragment.glsl'); - for (const fragmentShaderFile of fragmentShaderFiles) { - const fragmentShaderSource = fs.readFileSync(fragmentShaderFile, 'utf8'); - const fragmentShaderTranspiled = transpileToWebGL1('fragment', fragmentShaderSource); - expect(fragmentShaderSource.trim()).not.equals(fragmentShaderTranspiled.trim()); - } - }); });