Skip to content

Commit 50b4c7c

Browse files
committed
GPS-745: just don't render Cesium
1 parent 17e6862 commit 50b4c7c

3 files changed

Lines changed: 254 additions & 5 deletions

File tree

packages/mapviewer/cypress.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default defineConfig({
3131
setupNodeEvents(on, config) {
3232
// eslint-disable-next-line no-console
3333
console.log(`setupNodeEvents running!`)
34+
// this event will not fire on cypress runner!
3435
on('before:browser:launch', (browser, launchOptions) => {
3536
// eslint-disable-next-line no-console
3637
console.log(`Configuring browser: ${browser.name}`)

packages/mapviewer/src/modules/map/components/cesium/CesiumMap.vue

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useStore } from 'vuex'
1818
import { TERRAIN_URL } from '@/config/cesium.config'
1919
import { CESIUM_STATIC_PATH } from '@/config/map.config'
2020
import { IS_TESTING_WITH_CYPRESS } from '@/config/staging.config'
21+
import getWebGLStub from '@/modules/map/components/cesium/webGLStub.js'
2122
import CesiumBackgroundLayer from '@/modules/map/components/cesium/CesiumBackgroundLayer.vue'
2223
import CesiumCamera from '@/modules/map/components/cesium/CesiumCamera.vue'
2324
import CesiumGeolocationFeedback from '@/modules/map/components/cesium/CesiumGeolocationFeedback.vue'
@@ -106,11 +107,11 @@ async function createViewer() {
106107
useBrowserRecommendedResolution: true,
107108
terrainProvider: await CesiumTerrainProvider.fromUrl(TERRAIN_URL),
108109
requestRenderMode: true,
109-
// In headless CI environments Cesium runs on SwiftShader (software WebGL). SwiftShader
110-
// has WebGL2RenderingContext defined but canvas.getContext('webgl2') returns null because
111-
// there is no GPU. Cesium only auto-falls-back to WebGL1 when WebGL2RenderingContext is
112-
// undefined, so we must force requestWebgl1 explicitly. Anisotropic filtering and antialias
113-
// are also disabled as SwiftShader cannot initialize them.
110+
// In CI environments (Cypress Cloud) the browser has no GPU and canvas.getContext('webgl')
111+
// returns null, so Cesium cannot initialize normally. We detect this and fall back to
112+
// Cesium's official WebGL stub (ported from CesiumGS/cesium/Specs/getWebGLStub.js) which
113+
// replaces all WebGL calls with no-ops. The scene won't render visually but all app state
114+
// (layers, camera, feature selection) works correctly, which is what the tests assert on.
114115
...(IS_TESTING_WITH_CYPRESS
115116
? {
116117
contextOptions: {
@@ -119,6 +120,12 @@ async function createViewer() {
119120
antialias: false,
120121
failIfMajorPerformanceCaveat: false,
121122
},
123+
getWebGLStub: (() => {
124+
const canvas = document.createElement('canvas')
125+
const hasWebGL =
126+
canvas.getContext('webgl2') ?? canvas.getContext('webgl')
127+
return hasWebGL ? undefined : getWebGLStub
128+
})(),
122129
},
123130
}
124131
: {}),
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
// Ported from https://github.com/CesiumGS/cesium/blob/main/Specs/getWebGLStub.js
2+
// Used in CI environments where canvas.getContext('webgl') returns null (no GPU available).
3+
// Passed as contextOptions.getWebGLStub to the Cesium Viewer so it can initialize without
4+
// a real WebGL context. All draw calls are no-ops; the scene won't render but app state works.
5+
import { clone, WebGLConstants } from 'cesium'
6+
7+
export default function getWebGLStub(canvas, options) {
8+
const stub = clone(WebGLConstants)
9+
10+
stub.canvas = canvas
11+
stub.drawingBufferWidth = Math.max(canvas.width, 1)
12+
stub.drawingBufferHeight = Math.max(canvas.height, 1)
13+
14+
stub.activeTexture = noop
15+
stub.attachShader = noop
16+
stub.bindAttribLocation = noop
17+
stub.bindBuffer = noop
18+
stub.bindFramebuffer = noop
19+
stub.bindRenderbuffer = noop
20+
stub.bindTexture = noop
21+
stub.blendColor = noop
22+
stub.blendEquation = noop
23+
stub.blendEquationSeparate = noop
24+
stub.blendFunc = noop
25+
stub.blendFuncSeparate = noop
26+
stub.bufferData = noop
27+
stub.bufferSubData = noop
28+
stub.checkFramebufferStatus = () => WebGLConstants.FRAMEBUFFER_COMPLETE
29+
stub.clear = noop
30+
stub.clearColor = noop
31+
stub.clearDepth = noop
32+
stub.clearStencil = noop
33+
stub.colorMask = noop
34+
stub.compileShader = noop
35+
stub.compressedTexImage2D = noop
36+
stub.compressedTexSubImage2D = noop
37+
stub.copyTexImage2D = noop
38+
stub.copyTexSubImage2D = noop
39+
stub.createBuffer = createStub
40+
stub.createFramebuffer = createStub
41+
stub.createProgram = createStub
42+
stub.createRenderbuffer = createStub
43+
stub.createShader = createStub
44+
stub.createTexture = createStub
45+
stub.cullFace = noop
46+
stub.deleteBuffer = noop
47+
stub.deleteFramebuffer = noop
48+
stub.deleteProgram = noop
49+
stub.deleteRenderbuffer = noop
50+
stub.deleteShader = noop
51+
stub.deleteTexture = noop
52+
stub.depthFunc = noop
53+
stub.depthMask = noop
54+
stub.depthRange = noop
55+
stub.detachShader = noop
56+
stub.disable = noop
57+
stub.disableVertexAttribArray = noop
58+
stub.drawArrays = noop
59+
stub.drawElements = noop
60+
stub.enable = noop
61+
stub.enableVertexAttribArray = noop
62+
stub.finish = noop
63+
stub.flush = noop
64+
stub.framebufferRenderbuffer = noop
65+
stub.framebufferTexture2D = noop
66+
stub.frontFace = noop
67+
stub.generateMipmap = noop
68+
stub.getActiveAttrib = getStub
69+
stub.getActiveUniform = getStub
70+
stub.getAttachedShaders = () => []
71+
stub.getAttribLocation = getStub
72+
stub.getBufferParameter = () => null
73+
stub.getContextAttributes = () => ({
74+
alpha: options.alpha ?? true,
75+
depth: options.depth ?? true,
76+
stencil: options.stencil ?? false,
77+
antialias: options.antialias ?? true,
78+
premultipliedAlpha: options.premultipliedAlpha ?? true,
79+
preserveDrawingBuffer: options.preserveDrawingBuffer ?? false,
80+
powerPreference: options.powerPreference ?? 'default',
81+
failIfMajorPerformanceCaveat: options.failIfMajorPerformanceCaveat ?? false,
82+
})
83+
stub.getError = () => WebGLConstants.NO_ERROR
84+
stub.getExtension = getExtensionStub
85+
stub.getFramebufferAttachmentParameter = () => null
86+
stub.getParameter = getParameterStub(options)
87+
stub.getProgramParameter = getProgramParameterStub
88+
stub.getProgramInfoLog = getStub
89+
stub.getRenderbufferParameter = () => null
90+
stub.getShaderParameter = getShaderParameterStub
91+
stub.getShaderInfoLog = getStub
92+
stub.getShaderPrecisionFormat = getShaderPrecisionStub
93+
stub.getShaderSource = () => null
94+
stub.getSupportedExtensions = () => []
95+
stub.getTexParameter = () => null
96+
stub.getUniform = getStub
97+
stub.getUniformLocation = getStub
98+
stub.getVertexAttrib = () => null
99+
stub.getVertexAttribOffset = () => 0
100+
stub.hint = noop
101+
stub.isBuffer = () => false
102+
stub.isContextLost = () => false
103+
stub.isEnabled = () => false
104+
stub.isFramebuffer = () => false
105+
stub.isProgram = () => false
106+
stub.isRenderbuffer = () => false
107+
stub.isShader = () => false
108+
stub.isTexture = () => false
109+
stub.lineWidth = noop
110+
stub.linkProgram = noop
111+
stub.pixelStorei = noop
112+
stub.polygonOffset = noop
113+
stub.readPixels = noop
114+
stub.renderbufferStorage = noop
115+
stub.sampleCoverage = noop
116+
stub.scissor = noop
117+
stub.shaderSource = noop
118+
stub.stencilFunc = noop
119+
stub.stencilFuncSeparate = noop
120+
stub.stencilMask = noop
121+
stub.stencilMaskSeparate = noop
122+
stub.stencilOp = noop
123+
stub.stencilOpSeparate = noop
124+
stub.texParameterf = noop
125+
stub.texParameteri = noop
126+
stub.texImage2D = noop
127+
stub.texSubImage2D = noop
128+
stub.texStorage3D = noop
129+
stub.texImage3D = noop
130+
stub.texSubImage3D = noop
131+
stub.uniform1f = noop
132+
stub.uniform1fv = noop
133+
stub.uniform1i = noop
134+
stub.uniform1iv = noop
135+
stub.uniform2f = noop
136+
stub.uniform2fv = noop
137+
stub.uniform2i = noop
138+
stub.uniform2iv = noop
139+
stub.uniform3f = noop
140+
stub.uniform3fv = noop
141+
stub.uniform3i = noop
142+
stub.uniform3iv = noop
143+
stub.uniform4f = noop
144+
stub.uniform4fv = noop
145+
stub.uniform4i = noop
146+
stub.uniform4iv = noop
147+
stub.uniformMatrix2fv = noop
148+
stub.uniformMatrix3fv = noop
149+
stub.uniformMatrix4fv = noop
150+
stub.useProgram = noop
151+
stub.validateProgram = noop
152+
stub.vertexAttrib1f = noop
153+
stub.vertexAttrib1fv = noop
154+
stub.vertexAttrib2f = noop
155+
stub.vertexAttrib2fv = noop
156+
stub.vertexAttrib3f = noop
157+
stub.vertexAttrib3fv = noop
158+
stub.vertexAttrib4f = noop
159+
stub.vertexAttrib4fv = noop
160+
stub.vertexAttribPointer = noop
161+
stub.viewport = noop
162+
163+
// WebGL2 instanced drawing (used by Cesium internally)
164+
stub.drawElementsInstanced = noop
165+
stub.drawArraysInstanced = noop
166+
stub.vertexAttribDivisor = noop
167+
stub.drawBuffers = noop
168+
stub.createVertexArray = createStub
169+
stub.bindVertexArray = noop
170+
stub.deleteVertexArray = noop
171+
172+
return stub
173+
}
174+
175+
function noop() {}
176+
function createStub() {
177+
return {}
178+
}
179+
function getStub() {
180+
return {}
181+
}
182+
183+
function getExtensionStub(name) {
184+
if (name === 'ANGLE_instanced_arrays') {
185+
return {
186+
drawElementsInstancedANGLE: noop,
187+
drawArraysInstancedANGLE: noop,
188+
vertexAttribDivisorANGLE: noop,
189+
}
190+
}
191+
if (name === 'OES_texture_float') return {}
192+
if (name === 'WEBGL_draw_buffers') return { drawBuffersWEBGL: noop }
193+
return null
194+
}
195+
196+
function getParameterStub(options) {
197+
const values = {
198+
[WebGLConstants.STENCIL_BITS]: options.stencil ? 8 : 0,
199+
[WebGLConstants.MAX_COMBINED_TEXTURE_IMAGE_UNITS]: 32,
200+
[WebGLConstants.MAX_CUBE_MAP_TEXTURE_SIZE]: 16384,
201+
[WebGLConstants.MAX_FRAGMENT_UNIFORM_VECTORS]: 1024,
202+
[WebGLConstants.MAX_TEXTURE_IMAGE_UNITS]: 16,
203+
[WebGLConstants.MAX_RENDERBUFFER_SIZE]: 16384,
204+
[WebGLConstants.MAX_TEXTURE_SIZE]: 16384,
205+
[WebGLConstants.MAX_3D_TEXTURE_SIZE]: 2048,
206+
[WebGLConstants.MAX_VARYING_VECTORS]: 30,
207+
[WebGLConstants.MAX_VERTEX_ATTRIBS]: 16,
208+
[WebGLConstants.MAX_VERTEX_TEXTURE_IMAGE_UNITS]: 16,
209+
[WebGLConstants.MAX_VERTEX_UNIFORM_VECTORS]: 4096,
210+
[WebGLConstants.ALIASED_LINE_WIDTH_RANGE]: [1, 1],
211+
[WebGLConstants.ALIASED_POINT_SIZE_RANGE]: [1, 1024],
212+
[WebGLConstants.MAX_VIEWPORT_DIMS]: [16384, 16384],
213+
[WebGLConstants.MAX_TEXTURE_MAX_ANISOTROPY_EXT]: 16,
214+
[WebGLConstants.MAX_DRAW_BUFFERS]: 8,
215+
[WebGLConstants.MAX_COLOR_ATTACHMENTS]: 8,
216+
[WebGLConstants.MAX_SAMPLES]: 8,
217+
}
218+
return (pname) => values[pname] ?? 0
219+
}
220+
221+
function getProgramParameterStub(program, pname) {
222+
if (pname === WebGLConstants.LINK_STATUS || pname === WebGLConstants.VALIDATE_STATUS) {
223+
return true
224+
}
225+
if (pname === WebGLConstants.ACTIVE_UNIFORMS || pname === WebGLConstants.ACTIVE_ATTRIBUTES) {
226+
return 0
227+
}
228+
return null
229+
}
230+
231+
function getShaderParameterStub(shader, pname) {
232+
if (pname === WebGLConstants.COMPILE_STATUS) return true
233+
return null
234+
}
235+
236+
function getShaderPrecisionStub(shadertype, precisiontype) {
237+
if (precisiontype === WebGLConstants.HIGH_FLOAT) {
238+
return { rangeMin: 127, rangeMax: 127, precision: 23 }
239+
}
240+
return { rangeMin: 31, rangeMax: 30, precision: 0 }
241+
}

0 commit comments

Comments
 (0)