PolylineEntity - Path Jitters on windows and intel arc
What happened?
Hi all,
We are seeing a bug with some of our customers linework, that seems very similar to #9417
Except that its happening on windows with Intel Arc hardware.
All the zigzag lines you can see below are actually meant to be straight lines, and do render as straight when NOT using intel arc hardware. That is, they work fine on nvidia, apple silicon, AMD, etc.
We cannot easily repro since we don't have the hardware to hand (yet).
We do have a sandcastle setup (from the last time we observed this on apple silicon)
Note: we currently use cesium 1.128, we do not (yet) know if this is still an issue on later Cesium.
One workaround we have successfully tried with our customers is to switch the chrome/edge backend to using OpenGL, not ANGLE.
Reproduction steps
- Run sandcastle demo on chrome/edge on windows with intel arc hardware
- Observe jittery lines
- switch angle backend to OpenGL
- Observe correct lines
Sandcastle example
Environment
Browser: chrome/edge CesiumJS Version: 1.128 Operating System: Windows 11
Intel Arc GPU. ANGLE (not opengl)
Thank you for reporting this. Appears to be a hardware issue. Hopefully the team or community members can identify fixes or workarounds soon.
@lukemckinstry
From the last time the appeared (on chrome / apple silicon), it seemed to be a shader miscompile in some part of the chain of the graphics stack between browser and card.
It is unclear how the issue was ultimately fixed on apple silicon either, whether the upstream vendors fixed it or if Cesium worked around it somehow.
According to this comment on #9417
Twiddling near here (old version), fixed the miscompile.
packages/engine/Source/Shaders/Builtin/Functions/translateRelativeToEye.glsl
vec4 czm_translateRelativeToEye(vec3 high, vec3 low)
{
vec3 highDifference = high - czm_encodedCameraPositionMCHigh;
- vec3 lowDifference = low - czm_encodedCameraPositionMCLow;
+ vec3 lowDifference = (low - czm_encodedCameraPositionMCLow) * (1.0 + czm_epsilon7);
return vec4(highDifference + lowDifference, 1.0);
}
We would take a crack at twiddling the shader, but we don't have the hardware to hand yet, so cannot repro just yet.
Our current assumption is that ANGLE in chrome when using intel arc is optimizing something too aggressively when transpiling shaders and its breaking this part of Cesium.
@ggetz @lukemckinstry
We have got the Intel Arc hardware to hand and can confirm that the sandcastle is showing the bug on intel arc.
This is very very similar to #9417 and I can confirm that a similar (but different) workaround patch to czm_translateRelativeToEye affects the miscompile issue.
In our case, we do this
// Essentially we are replacing this miscompiling code on windows/Intel arc:
//
// vec4 czm_translateRelativeToEye(vec3 high, vec3 low)
// {
// vec3 highDifference = high - czm_encodedCameraPositionMCHigh;
// if (length(highDifference) == 0.0) { highDifference = vec3(0); }
// vec3 lowDifference = low - czm_encodedCameraPositionMCLow;
// return vec4(highDifference + lowDifference, 1.0);
// }
//
// With this workaround (it works around the faulty fusion):
//
// vec4 czm_translateRelativeToEye(vec3 high, vec3 low)
// {
// vec3 highDifference = high - czm_encodedCameraPositionMCHigh;
// if (length(highDifference) == 0.0) { highDifference = vec3(0.0); }
// vec3 lowDifference = mat3(1.0) * (low - czm_encodedCameraPositionMCLow);
// return vec4(highDifference + lowDifference, 1.0);
// }
Which seems to alleviate the error in some (but not all cases).
Can you please take a closer look at this issue and raise with the vendor(s).
As mentioned here, I've found a decent workaround for this if you don't need the 2D and CV modes. Just construct your viewer like this:
const viewer = new Cesium.Viewer("cesiumContainer", {
scene3DOnly: true
});
Ok so I think I understand what is happening. I wouldn't exactly call it a driver bug. More like an overly aggressive optimizer. I've been looking at this simpler Sandcastle, which jitters around badly on my Intel Arc A750. You can find a GIF of the jittering effect here.
Ok so Primitive.js generates some GLSL code that looks like this:
vec4 czm_computePosition() {
vec4 p;
if (czm_morphTime == 1.0) {
p = czm_translateRelativeToEye(position3DHigh, position3DLow);
}
else if (czm_morphTime == 0.0) {
p = czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy);
}
else {
p = czm_columbusViewMorph(
czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy),
czm_translateRelativeToEye(position3DHigh, position3DLow),
czm_morphTime);
}
return p;
}
Now if we look closely at this code, we'll notice that those first two branches are completely pointless. Kind of. czm_columbusViewMorph is identical to mix. So when the t parameter is 0.0, mix effectively returns the first parameter, and when it's 1.0 it effectively returns the second one. Which is exactly what the first two branches are doing explicitly.
So, this entire conditional seems to serve no purpose other than saving a couple of (very cheap) math operations. In fact, drivers are allowed to evaluate an if using what HLSL calls "flatten" mode, where it evaluates both sides of the conditional and then picks one, rather than actually branching. And in flatten mode, we don't even save the few math operations, but we do avoid a branch. So it's actually pretty reasonable for a clever driver to rewrite this as:
vec4 czm_computePosition() {
return czm_columbusViewMorph(
czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy),
czm_translateRelativeToEye(position3DHigh, position3DLow),
czm_morphTime);
}
Ok, so let's try doing that ourselves manually, and running it on an NVidia GPU. Ah hah! It jitters around just like on the Intel GPU.
The problem, as always, is precision. mix(a, b, t) is not identical to b when t=1.0. Instead, it's some math that evaluates to b within the limits of floating point precision. When a and b are huge numbers (like they are in our case), it'll be close to b, but not exactly. The positions will jitter around.
So our problem here is we have a conditional that the Intel driver is legitimately optimizing, except that the general case it optimizes to is imprecise because of the unreasonably large numbers involved. (And yes, we totally have jittering problems while morphing between scene modes. It's just hard to tell, and harder to fix.)
So how do we fix this? Well, as mentioned above, setting scene3DOnly=true works because it avoids this morphing-related code altogether. I'm pretty sure we could fix this at the HLSL level by tagging this conditional with the [branch] attribute, which would forbid the compiler from flattening and optimizing it. But as far as I can tell there's no way to do that from GLSL. One thing I've found to work is to rewrite the function like this:
vec4 czm_computePosition() {
switch(int(czm_sceneMode)) {
case 1: // CV
case 2: // 2D
return czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy);
case 3: // 3D
return czm_translateRelativeToEye(position3DHigh, position3DLow);
default: // Morphing
return czm_columbusViewMorph(
czm_translateRelativeToEye(position2DHigh.zxy, position2DLow.zxy),
czm_translateRelativeToEye(position3DHigh, position3DLow),
czm_morphTime);
}
}
I think this works because the compiler is sufficiently discouraged from flattening (executing all branches of) this switch.
The main problem with this is that it will only work in WebGL2, because of the use of int. I'm also not sure if there could be any performance cost to this. I expect it'll be small-to-zero, but a driver that treats this as a truly dynamic branch (rather than one that is guaranteed to always take the same path because it's switching on a uniform) could see much lower performance.
Great, @kring, this looks like it will stop the faulty optimization in its track. I will give it a crack on my Intel Arc too.