mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix(render): keep Cesium's model atmosphere off Apple Metal
iPad and iPhone show "An error occurred while rendering. Rendering has
stopped." on load. Cesium's AtmosphereStageVS calls
czm_computeGroundAtmosphereScattering with its `out` parameters bound
directly to varyings:
czm_computeGroundAtmosphereScattering(
v_positionWC, lightDirection,
v_atmosphereRayleighColor, v_atmosphereMieColor,
v_atmosphereOpacity);
ANGLE's Metal backend wraps each `out` argument in an ANGLE_out(...)
helper taking `thread T &`, but vertex-output members live in
`__metal_generic` and cannot bind to `thread`, so the program fails to
LINK and Cesium tears down the render loop.
This is the only shader in Cesium that binds varyings to `out` params --
SkyAtmosphere (czm_computeScattering) and the ground-atmosphere fragment
path both route through locals, so they are unaffected and stay on.
ModelSceneGraph.configurePipeline attaches the stage under a single
condition, `fog.enabled && fog.renderable`, so clearing `fog.renderable`
keeps the broken program from ever being built.
`renderable` rather than `enabled` is deliberate: Fog.update returns
early when `enabled` is false and forfeits the density that drives 3D
Tiles screen-space-error scaling, and visualSettings saves/restores
`fog.enabled` around IR boost, which would resurrect the broken shader
mid-session.
Detection is a WebGL2 link probe of the same out-param/varying pattern,
so a future driver fix silently restores the effect with no code change,
OR'd with iOS/iPadOS platform detection as a backstop for when no probe
context can be created. iPadOS desktop mode is identified by the
Macintosh UA token plus maxTouchPoints > 1, since real Macs report zero
and navigator.platform is deprecated.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HoRD3yHVgfTHeikTmsUoHf
This commit is contained in:
1 parent
082074a006
commit
b456eb8ff1
7 files changed
+424
-1
No files matched your search
@@ -103,6 +103,18 @@
|
|||||||
Add feed provenance to analyst/view answers and HUD context while retaining existing response fields and runner ownership (Matt Van Horn, #347).
|
Add feed provenance to analyst/view answers and HUD context while retaining existing response fields and runner ownership (Matt Van Horn, #347).
|
||||||
|
|
||||||
Analyst records for loaded satellites, datacenters and dams, with explicit bounded count/rank coverage (Matt Van Horn, #351).
|
Analyst records for loaded satellites, datacenters and dams, with explicit bounded count/rank coverage (Matt Van Horn, #351).
|
||||||
|
- Render on iPad and iPhone instead of stopping with "An error occurred while
|
||||||
|
rendering." Cesium's per-vertex model atmosphere binds shader `out`
|
||||||
|
parameters directly to varyings, which Apple's Metal/ANGLE backend cannot
|
||||||
|
link, so the program failed and the render loop was torn down. The stage is
|
||||||
|
now kept out of the pipeline on affected devices by clearing
|
||||||
|
`scene.fog.renderable`, which leaves `fog.enabled` — and the fog density that
|
||||||
|
drives 3D Tiles refinement — untouched. Detection is a WebGL2 link probe of
|
||||||
|
the same pattern, so a future driver fix restores the effect with no code
|
||||||
|
change, with iOS/iPadOS detection as a backstop. Sky atmosphere and the
|
||||||
|
ground-atmosphere fragment path route through locals and are unaffected.
|
||||||
|
`src/app/atmosphereCompat.test.mjs` pins the probe, the platform matrix and
|
||||||
|
the `renderable`-not-`enabled` choice.
|
||||||
|
|
||||||
- Remove the spurious scrollbars that appeared on both panel stacks at narrow
|
- Remove the spurious scrollbars that appeared on both panel stacks at narrow
|
||||||
widths (720px and below) as soon as a panel was expanded. The stacks scroll
|
widths (720px and below) as soon as a panel was expanded. The stacks scroll
|
||||||
|
|||||||
@@ -664,6 +664,24 @@ snapshots, lifecycle actions and row descriptors. Remount and teardown remove
|
|||||||
listeners and row subscriptions; obsolete completions do not repaint old rows.
|
listeners and row subscriptions; obsolete completions do not repaint old rows.
|
||||||
The clear control presents busy state while its existing action owns the transaction.
|
The clear control presents busy state while its existing action owns the transaction.
|
||||||
|
|
||||||
|
|
||||||
|
## Model atmosphere on Apple Metal
|
||||||
|
|
||||||
|
Cesium's per-vertex model atmosphere is kept out of the pipeline on devices
|
||||||
|
whose driver cannot link it. `AtmosphereStageVS` binds shader `out` parameters
|
||||||
|
directly to varyings, which ANGLE's Metal backend rejects at link time, tearing
|
||||||
|
down the render loop on iPadOS and iOS. `ModelSceneGraph.configurePipeline`
|
||||||
|
attaches that stage only when `fog.enabled && fog.renderable`, so the viewer
|
||||||
|
clears `scene.fog.renderable` and leaves `fog.enabled` — and the fog density
|
||||||
|
that drives 3D Tiles screen-space-error scaling — intact. Sky atmosphere and the
|
||||||
|
ground-atmosphere fragment path route through locals and are unaffected.
|
||||||
|
|
||||||
|
Detection is a WebGL2 link probe of the same out-parameter/varying pattern, so a
|
||||||
|
future driver fix restores the effect with no code change; iOS/iPadOS platform
|
||||||
|
detection is the backstop for when no probe context can be created. Applied
|
||||||
|
during viewer construction, before any tile builds a draw command.
|
||||||
|
|
||||||
|
|
||||||
## Map Source control ownership
|
## Map Source control ownership
|
||||||
|
|
||||||
Map Source controls own chip listeners, source-state subscriptions and selection
|
Map Source controls own chip listeners, source-state subscriptions and selection
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"src/app/mapLifecycle.test.mjs",
|
"src/app/mapLifecycle.test.mjs",
|
||||||
"src/app/startupCamera.test.mjs",
|
"src/app/startupCamera.test.mjs",
|
||||||
"src/app/viewer.js",
|
"src/app/viewer.js",
|
||||||
|
"src/app/atmosphereCompat.test.mjs",
|
||||||
"src/standalone/application.js",
|
"src/standalone/application.js",
|
||||||
"src/standalone/controls.js",
|
"src/standalone/controls.js",
|
||||||
"src/standalone/data.js",
|
"src/standalone/data.js",
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"exports": ["./application/viewer"],
|
"exports": ["./application/viewer"],
|
||||||
"modules": ["src/app/viewer.js"],
|
"modules": ["src/app/viewer.js", "src/app/atmosphereCompat.js"],
|
||||||
"external": ["cesium"]
|
"external": ["cesium"]
|
||||||
},
|
},
|
||||||
"vite-build": {
|
"vite-build": {
|
||||||
@@ -1287,6 +1287,7 @@
|
|||||||
"src/app/surfaceServices.js",
|
"src/app/surfaceServices.js",
|
||||||
"src/app/tools.js",
|
"src/app/tools.js",
|
||||||
"src/app/viewer.js",
|
"src/app/viewer.js",
|
||||||
|
"src/app/atmosphereCompat.js",
|
||||||
"src/bloom.js",
|
"src/bloom.js",
|
||||||
"src/camera.js",
|
"src/camera.js",
|
||||||
"src/cameraVerbs.js",
|
"src/cameraVerbs.js",
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/**
|
||||||
|
* Workaround for Apple's Metal/ANGLE shader translator rejecting Cesium's
|
||||||
|
* per-vertex model atmosphere.
|
||||||
|
*
|
||||||
|
* Cesium's `AtmosphereStageVS` calls a function whose `out` parameters are
|
||||||
|
* bound DIRECTLY to varyings:
|
||||||
|
*
|
||||||
|
* czm_computeGroundAtmosphereScattering(
|
||||||
|
* v_positionWC, lightDirection,
|
||||||
|
* v_atmosphereRayleighColor, v_atmosphereMieColor, v_atmosphereOpacity);
|
||||||
|
*
|
||||||
|
* ANGLE's Metal backend wraps each `out` argument in an `ANGLE_out(...)`
|
||||||
|
* helper that takes a `thread T &`. Vertex-output struct members live in
|
||||||
|
* `__metal_generic`, which cannot bind to `thread`, so the program fails to
|
||||||
|
* LINK (not compile) and Cesium tears the render loop down:
|
||||||
|
*
|
||||||
|
* RuntimeError: Program failed to link. ... MSL compilation error:
|
||||||
|
* reference to type 'thread float3' could not bind to an lvalue of type
|
||||||
|
* '__metal_generic float3'
|
||||||
|
*
|
||||||
|
* This is the only shader in Cesium that binds varyings to `out` params --
|
||||||
|
* SkyAtmosphere and the ground-atmosphere fragment path both route through
|
||||||
|
* locals first, so they are unaffected and stay on.
|
||||||
|
*
|
||||||
|
* Cesium attaches that stage in `ModelSceneGraph.configurePipeline` under a
|
||||||
|
* single condition:
|
||||||
|
*
|
||||||
|
* const fogRenderable = frameState.fog.enabled && frameState.fog.renderable;
|
||||||
|
* if (fogRenderable) modelPipelineStages.push(AtmospherePipelineStage);
|
||||||
|
*
|
||||||
|
* so clearing `scene.fog.renderable` keeps the broken stage out of the
|
||||||
|
* pipeline entirely -- the program is never built, so it cannot fail to link.
|
||||||
|
*
|
||||||
|
* We clear `renderable` rather than `enabled` deliberately:
|
||||||
|
* - `Fog.update` still computes fog density when only `renderable` is off,
|
||||||
|
* preserving the distance-based screen-space-error scaling that governs
|
||||||
|
* 3D Tiles refinement. Clearing `enabled` returns early and forfeits it.
|
||||||
|
* - `visualSettings.js` saves and restores `scene.fog.enabled` around IR
|
||||||
|
* boost. Parking our fix on `enabled` would let that restore resurrect
|
||||||
|
* the broken shader mid-session; `renderable` is orthogonal to it.
|
||||||
|
*
|
||||||
|
* @module app/atmosphereCompat
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Minimal vertex shader reproducing the exact out-param-to-varying bind. */
|
||||||
|
const PROBE_VERTEX_SHADER = `#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec3 a_position;
|
||||||
|
out vec3 v_probe;
|
||||||
|
void writeOut(out vec3 value) { value = vec3(1.0); }
|
||||||
|
void main() {
|
||||||
|
writeOut(v_probe);
|
||||||
|
gl_Position = vec4(a_position, 1.0);
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const PROBE_FRAGMENT_SHADER = `#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec3 v_probe;
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() { fragColor = vec4(v_probe, 1.0); }`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the user agent is iOS or iPadOS.
|
||||||
|
*
|
||||||
|
* iPadOS 13+ defaults to desktop-mode browsing and reports `MacIntel` with no
|
||||||
|
* iPad token, so it is identified by touch points instead -- real Macs report
|
||||||
|
* `maxTouchPoints` of 0, including Apple Silicon ones that also claim
|
||||||
|
* `MacIntel`. Brave/Chrome/Firefox on iOS are all WebKit underneath and share
|
||||||
|
* the same Metal backend, so matching the platform rather than the browser is
|
||||||
|
* what we want.
|
||||||
|
*/
|
||||||
|
export function isAppleMobilePlatform(navigatorLike = globalThis.navigator) {
|
||||||
|
if (!navigatorLike) return false;
|
||||||
|
const ua = String(navigatorLike.userAgent || '');
|
||||||
|
if (/\b(iPad|iPhone|iPod)\b/.test(ua)) return true;
|
||||||
|
// Desktop-mode iPadOS presents as a Mac. What separates it from a real Mac
|
||||||
|
// is touch: Macs report 0 touch points, including Apple Silicon ones that
|
||||||
|
// also claim `MacIntel`. `navigator.platform` is deprecated and may be
|
||||||
|
// empty, so the UA's Macintosh token is accepted as an equal signal rather
|
||||||
|
// than relying on platform alone.
|
||||||
|
const platform = String(navigatorLike.platform || '');
|
||||||
|
const macLike =
|
||||||
|
platform === 'MacIntel' ||
|
||||||
|
platform === 'MacARM' ||
|
||||||
|
/\bMacintosh\b/.test(ua);
|
||||||
|
const touchPoints = Number(navigatorLike.maxTouchPoints) || 0;
|
||||||
|
return macLike && touchPoints > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link the probe program and report whether the driver rejects the pattern.
|
||||||
|
*
|
||||||
|
* Returns `false` when WebGL2 is unavailable so that a missing context is
|
||||||
|
* never mistaken for a broken driver -- Cesium has its own handling for that,
|
||||||
|
* and the caller still falls back to platform detection.
|
||||||
|
*/
|
||||||
|
export function probeOutParamVaryingLinkFailure(gl) {
|
||||||
|
if (!gl) return false;
|
||||||
|
const vs = gl.createShader(gl.VERTEX_SHADER);
|
||||||
|
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
||||||
|
const program = gl.createProgram();
|
||||||
|
if (!vs || !fs || !program) return false;
|
||||||
|
try {
|
||||||
|
gl.shaderSource(vs, PROBE_VERTEX_SHADER);
|
||||||
|
gl.compileShader(vs);
|
||||||
|
gl.shaderSource(fs, PROBE_FRAGMENT_SHADER);
|
||||||
|
gl.compileShader(fs);
|
||||||
|
// A compile failure here is not the bug we are probing for: the driver
|
||||||
|
// rejects this pattern at LINK time, after both stages compile cleanly.
|
||||||
|
if (
|
||||||
|
!gl.getShaderParameter(vs, gl.COMPILE_STATUS) ||
|
||||||
|
!gl.getShaderParameter(fs, gl.COMPILE_STATUS)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
gl.attachShader(program, vs);
|
||||||
|
gl.attachShader(program, fs);
|
||||||
|
gl.linkProgram(program);
|
||||||
|
return !gl.getProgramParameter(program, gl.LINK_STATUS);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
gl.deleteShader(vs);
|
||||||
|
gl.deleteShader(fs);
|
||||||
|
gl.deleteProgram(program);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build a throwaway WebGL2 context for the probe, or null if unavailable. */
|
||||||
|
function defaultProbeContext() {
|
||||||
|
try {
|
||||||
|
const canvas =
|
||||||
|
typeof OffscreenCanvas === 'function'
|
||||||
|
? new OffscreenCanvas(1, 1)
|
||||||
|
: globalThis.document?.createElement('canvas');
|
||||||
|
return canvas?.getContext('webgl2') ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decide whether the per-vertex model atmosphere must be kept out of the
|
||||||
|
* pipeline on this device.
|
||||||
|
*
|
||||||
|
* The probe is authoritative when it fires, so a future driver fix silently
|
||||||
|
* restores the effect without a code change. Platform detection is the
|
||||||
|
* backstop for the case where no context can be created -- on Apple mobile a
|
||||||
|
* link failure is fatal and unrecoverable, so we would rather lose fog than
|
||||||
|
* gamble the whole render loop on the probe having run.
|
||||||
|
*/
|
||||||
|
export function shouldDisableModelAtmosphere({
|
||||||
|
navigatorLike = globalThis.navigator,
|
||||||
|
createProbeContext = defaultProbeContext,
|
||||||
|
} = {}) {
|
||||||
|
const gl = createProbeContext();
|
||||||
|
if (gl) {
|
||||||
|
if (probeOutParamVaryingLinkFailure(gl)) return true;
|
||||||
|
// Probe ran and the driver accepted the pattern. Apple mobile is still
|
||||||
|
// quarantined: the probe program is far simpler than Cesium's, and a
|
||||||
|
// false negative there costs the entire scene.
|
||||||
|
return isAppleMobilePlatform(navigatorLike);
|
||||||
|
}
|
||||||
|
return isAppleMobilePlatform(navigatorLike);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep Cesium's broken model-atmosphere stage out of the pipeline.
|
||||||
|
*
|
||||||
|
* Returns true when the workaround was applied. Safe to call on any scene;
|
||||||
|
* scenes without a fog object are left untouched.
|
||||||
|
*/
|
||||||
|
export function applyModelAtmosphereWorkaround(scene, options = {}) {
|
||||||
|
if (!scene?.fog) return false;
|
||||||
|
if (!shouldDisableModelAtmosphere(options)) return false;
|
||||||
|
scene.fog.renderable = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {
|
||||||
|
isAppleMobilePlatform,
|
||||||
|
probeOutParamVaryingLinkFailure,
|
||||||
|
shouldDisableModelAtmosphere,
|
||||||
|
applyModelAtmosphereWorkaround,
|
||||||
|
} from './atmosphereCompat.js';
|
||||||
|
|
||||||
|
const IPAD_OS12 = {
|
||||||
|
userAgent:
|
||||||
|
'Mozilla/5.0 (iPad; CPU OS 12_5 like Mac OS X) AppleWebKit/605.1.15',
|
||||||
|
platform: 'iPad',
|
||||||
|
maxTouchPoints: 5,
|
||||||
|
};
|
||||||
|
// iPadOS 13+ desktop mode: no iPad token, reports as a Mac.
|
||||||
|
const IPAD_DESKTOP_MODE = {
|
||||||
|
userAgent:
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
|
||||||
|
platform: 'MacIntel',
|
||||||
|
maxTouchPoints: 5,
|
||||||
|
};
|
||||||
|
const MAC_DESKTOP = {
|
||||||
|
userAgent:
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
|
||||||
|
platform: 'MacIntel',
|
||||||
|
maxTouchPoints: 0,
|
||||||
|
};
|
||||||
|
const LINUX_DESKTOP = {
|
||||||
|
userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/141',
|
||||||
|
platform: 'Linux x86_64',
|
||||||
|
maxTouchPoints: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Minimal GL double. `linkOk: false` mimics the Metal translator's refusal. */
|
||||||
|
function fakeGl({ linkOk = true, compileOk = true, throwOn = null } = {}) {
|
||||||
|
const deleted = { shaders: 0, programs: 0 };
|
||||||
|
return {
|
||||||
|
deleted,
|
||||||
|
VERTEX_SHADER: 1,
|
||||||
|
FRAGMENT_SHADER: 2,
|
||||||
|
COMPILE_STATUS: 3,
|
||||||
|
LINK_STATUS: 4,
|
||||||
|
createShader: () => ({}),
|
||||||
|
createProgram: () => ({}),
|
||||||
|
shaderSource() {
|
||||||
|
if (throwOn === 'shaderSource') throw new Error('context lost');
|
||||||
|
},
|
||||||
|
compileShader() {},
|
||||||
|
attachShader() {},
|
||||||
|
linkProgram() {},
|
||||||
|
getShaderParameter: () => compileOk,
|
||||||
|
getProgramParameter: () => linkOk,
|
||||||
|
deleteShader: () => (deleted.shaders += 1),
|
||||||
|
deleteProgram: () => (deleted.programs += 1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('identifies iPadOS in both legacy and desktop-mode guises', () => {
|
||||||
|
assert.equal(isAppleMobilePlatform(IPAD_OS12), true);
|
||||||
|
assert.equal(isAppleMobilePlatform(IPAD_DESKTOP_MODE), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not mistake a real Mac or a Linux desktop for Apple mobile', () => {
|
||||||
|
// Regression guard: the desktop-mode check keys on touch points, and a Mac
|
||||||
|
// reports the same MacIntel platform string with zero of them.
|
||||||
|
assert.equal(isAppleMobilePlatform(MAC_DESKTOP), false);
|
||||||
|
assert.equal(isAppleMobilePlatform(LINUX_DESKTOP), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats a missing navigator as not-Apple rather than throwing', () => {
|
||||||
|
assert.equal(isAppleMobilePlatform(undefined), false);
|
||||||
|
assert.equal(isAppleMobilePlatform(null), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detects desktop-mode iPadOS without navigator.platform', () => {
|
||||||
|
// `navigator.platform` is deprecated and can be absent or stripped; the UA's
|
||||||
|
// Macintosh token plus touch points has to carry the detection on its own.
|
||||||
|
assert.equal(
|
||||||
|
isAppleMobilePlatform({
|
||||||
|
userAgent:
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
|
||||||
|
maxTouchPoints: 5,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// Same UA, no touch: a real Mac, which must stay untouched.
|
||||||
|
assert.equal(
|
||||||
|
isAppleMobilePlatform({
|
||||||
|
userAgent:
|
||||||
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
|
||||||
|
maxTouchPoints: 0,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('probe reports failure only when the program fails to link', () => {
|
||||||
|
assert.equal(
|
||||||
|
probeOutParamVaryingLinkFailure(fakeGl({ linkOk: false })),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
probeOutParamVaryingLinkFailure(fakeGl({ linkOk: true })),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a compile failure is not reported as the link bug', () => {
|
||||||
|
// The Metal defect surfaces at link time; both stages compile cleanly.
|
||||||
|
// Treating a compile error as the bug would disable fog for the wrong reason.
|
||||||
|
assert.equal(
|
||||||
|
probeOutParamVaryingLinkFailure(
|
||||||
|
fakeGl({ compileOk: false, linkOk: false }),
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('probe releases its GL objects on both paths', () => {
|
||||||
|
const ok = fakeGl({ linkOk: true });
|
||||||
|
probeOutParamVaryingLinkFailure(ok);
|
||||||
|
assert.equal(ok.deleted.shaders, 2);
|
||||||
|
assert.equal(ok.deleted.programs, 1);
|
||||||
|
|
||||||
|
const thrown = fakeGl({ throwOn: 'shaderSource' });
|
||||||
|
probeOutParamVaryingLinkFailure(thrown);
|
||||||
|
assert.equal(thrown.deleted.shaders, 2, 'shaders freed even when GL throws');
|
||||||
|
assert.equal(thrown.deleted.programs, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('probe treats a null context as healthy, not broken', () => {
|
||||||
|
assert.equal(probeOutParamVaryingLinkFailure(null), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a failing probe disables the stage on any platform', () => {
|
||||||
|
assert.equal(
|
||||||
|
shouldDisableModelAtmosphere({
|
||||||
|
navigatorLike: LINUX_DESKTOP,
|
||||||
|
createProbeContext: () => fakeGl({ linkOk: false }),
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a passing probe still quarantines Apple mobile but frees desktops', () => {
|
||||||
|
const createProbeContext = () => fakeGl({ linkOk: true });
|
||||||
|
assert.equal(
|
||||||
|
shouldDisableModelAtmosphere({
|
||||||
|
navigatorLike: IPAD_DESKTOP_MODE,
|
||||||
|
createProbeContext,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
shouldDisableModelAtmosphere({
|
||||||
|
navigatorLike: LINUX_DESKTOP,
|
||||||
|
createProbeContext,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('falls back to platform detection when no context can be made', () => {
|
||||||
|
const createProbeContext = () => null;
|
||||||
|
assert.equal(
|
||||||
|
shouldDisableModelAtmosphere({
|
||||||
|
navigatorLike: IPAD_OS12,
|
||||||
|
createProbeContext,
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
shouldDisableModelAtmosphere({
|
||||||
|
navigatorLike: MAC_DESKTOP,
|
||||||
|
createProbeContext,
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('applies to the scene by clearing renderable, never enabled', () => {
|
||||||
|
// `enabled` must survive: Fog.update short-circuits when it is false, which
|
||||||
|
// forfeits the density used for 3D Tiles screen-space-error scaling, and
|
||||||
|
// visualSettings' IR boost saves/restores that same flag.
|
||||||
|
const scene = { fog: { enabled: true, renderable: true } };
|
||||||
|
const applied = applyModelAtmosphereWorkaround(scene, {
|
||||||
|
navigatorLike: IPAD_DESKTOP_MODE,
|
||||||
|
createProbeContext: () => null,
|
||||||
|
});
|
||||||
|
assert.equal(applied, true);
|
||||||
|
assert.equal(scene.fog.renderable, false);
|
||||||
|
assert.equal(scene.fog.enabled, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('leaves unaffected devices completely untouched', () => {
|
||||||
|
const scene = { fog: { enabled: true, renderable: true } };
|
||||||
|
const applied = applyModelAtmosphereWorkaround(scene, {
|
||||||
|
navigatorLike: LINUX_DESKTOP,
|
||||||
|
createProbeContext: () => fakeGl({ linkOk: true }),
|
||||||
|
});
|
||||||
|
assert.equal(applied, false);
|
||||||
|
assert.equal(scene.fog.renderable, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tolerates a scene with no fog object', () => {
|
||||||
|
assert.equal(applyModelAtmosphereWorkaround({}, {}), false);
|
||||||
|
assert.equal(applyModelAtmosphereWorkaround(null, {}), false);
|
||||||
|
});
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
|
import { applyModelAtmosphereWorkaround } from './atmosphereCompat.js';
|
||||||
|
|
||||||
const PINCH_ZOOM_MULTIPLIER = 8;
|
const PINCH_ZOOM_MULTIPLIER = 8;
|
||||||
const MAX_PINCH_PIXEL_DELTA = 120;
|
const MAX_PINCH_PIXEL_DELTA = 120;
|
||||||
@@ -124,6 +125,10 @@ export function createApplicationViewer({ container, creditContainer }) {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
viewer.targetFrameRate = 60;
|
viewer.targetFrameRate = 60;
|
||||||
|
// Before any tile builds a draw command: Cesium's per-vertex model
|
||||||
|
// atmosphere fails to LINK on Apple's Metal backend and kills the
|
||||||
|
// render loop. See app/atmosphereCompat.js.
|
||||||
|
applyModelAtmosphereWorkaround(viewer.scene);
|
||||||
viewer.scene.globe.show = false;
|
viewer.scene.globe.show = false;
|
||||||
viewer.scene.skyAtmosphere.show = true;
|
viewer.scene.skyAtmosphere.show = true;
|
||||||
viewer.scene.skyAtmosphere.atmosphereLightIntensity = 18;
|
viewer.scene.skyAtmosphere.atmosphereLightIntensity = 18;
|
||||||
|
|||||||
Reference in new issue
Block a user