-
-
Notifications
You must be signed in to change notification settings - Fork 745
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
Dual-Stack WebGL Runtime with WebGL2 to WebGL1 Fallback #5198
Changes from all commits
be56ece
821cf47
711a46b
ef2e836
ebf3481
1dfe227
75e65b2
1c33412
bc51178
9ca21fc
f3d991c
f59c420
09266ce
e52e6da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
import {type PreparedShader, shaders} 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'; | ||
import {isWebGL2} from '../gl/webgl2'; | ||
|
||
import type {SegmentVector} from '../data/segment'; | ||
import type {VertexBuffer} from '../gl/vertex_buffer'; | ||
|
@@ -72,6 +73,9 @@ | |
} | ||
|
||
const defines = configuration ? configuration.defines() : []; | ||
if (isWebGL2(gl)) { | ||
defines.unshift('#version 300 es'); | ||
} | ||
Comment on lines
+76
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a WebGL2 requirement in every shader source code, if we're on WebGL2. |
||
if (showOverdrawInspector) { | ||
defines.push('#define OVERDRAW_INSPECTOR;'); | ||
} | ||
|
@@ -82,8 +86,13 @@ | |
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 = transpileFragmentShaderToWebGL1(fragmentSource); | ||
vertexSource = transpileVertexShaderToWebGL1(vertexSource); | ||
} | ||
|
||
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); | ||
if (gl.isContextLost()) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a small rename and proper jsdoc comment on the function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a small rename and proper jsdoc comment on the function. |
||
|
||
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 | ||
Comment on lines
-134
to
+132
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. now that we assume original shaders to be in WebGL2, we should update our attribute and uniform extraction/scanning code. |
||
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 | ||
Comment on lines
-157
to
+156
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ same as above |
||
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 | ||
Comment on lines
-188
to
+186
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^ same as above |
||
uniform ${precision} ${type} u_${name}; | ||
#endif | ||
|
@@ -213,5 +211,22 @@ uniform ${precision} ${type} u_${name}; | |
} | ||
}); | ||
|
||
return {fragmentSource, vertexSource, staticAttributes, staticUniforms}; | ||
return {fragmentSource, vertexSource, staticAttributes: vertexAttributes, staticUniforms: shaderUniforms}; | ||
} | ||
|
||
/** 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('); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import {transpileVertexShaderToWebGL1, transpileFragmentShaderToWebGL1} from '../../src/shaders/shaders'; | ||
import {describe, test, expect} from 'vitest'; | ||
import {globSync} from 'glob'; | ||
import fs from 'fs'; | ||
|
||
describe('Shaders', () => { | ||
test('webgl2 to webgl1 transpiled shaders 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 = 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('../../src/shaders/*.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); | ||
} | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this behavior is moved entirely into
transpileToWebGL1()
function inshaders.ts
to be executed at runtime (also seeprogram.ts
changes).