Skip to content

Commit 3c01212

Browse files
slak44floryst
authored andcommitted
fix: avoid norm16 for textures with linear filtering
This only happens on certain devices due to a driver bug, see KhronosGroup/WebGL#3706
1 parent 66a6cdb commit 3c01212

File tree

2 files changed

+153
-15
lines changed

2 files changed

+153
-15
lines changed

Sources/Rendering/OpenGL/Texture/index.js

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import vtkViewNode from 'vtk.js/Sources/Rendering/SceneGraph/ViewNode';
77

88
import { registerOverride } from 'vtk.js/Sources/Rendering/OpenGL/ViewNodeFactory';
99

10+
import supportsNorm16Linear from './supportsNorm16Linear';
11+
1012
const { Wrap, Filter } = Constants;
1113
const { VtkDataTypes } = vtkDataArray;
1214
const { vtkDebugMacro, vtkErrorMacro, vtkWarningMacro } = macro;
@@ -160,6 +162,17 @@ function vtkOpenGLTexture(publicAPI, model) {
160162
}
161163
};
162164

165+
const getNorm16Ext = () => {
166+
if (
167+
(model.minificationFilter === Filter.LINEAR ||
168+
model.magnificationFilter === Filter.LINEAR) &&
169+
!supportsNorm16Linear()
170+
) {
171+
return undefined;
172+
}
173+
return model.oglNorm16Ext;
174+
};
175+
163176
//----------------------------------------------------------------------------
164177
publicAPI.destroyTexture = () => {
165178
// deactivate it first
@@ -391,7 +404,7 @@ function vtkOpenGLTexture(publicAPI, model) {
391404
result = model._openGLRenderWindow.getDefaultTextureInternalFormat(
392405
vtktype,
393406
numComps,
394-
model.oglNorm16Ext,
407+
getNorm16Ext(),
395408
publicAPI.useHalfFloat()
396409
);
397410
if (result) {
@@ -478,9 +491,9 @@ function vtkOpenGLTexture(publicAPI, model) {
478491
return model.context.UNSIGNED_BYTE;
479492
// prefer norm16 since that is accurate compared to
480493
// half float which is not
481-
case model.oglNorm16Ext && !useHalfFloat && VtkDataTypes.SHORT:
494+
case getNorm16Ext() && !useHalfFloat && VtkDataTypes.SHORT:
482495
return model.context.SHORT;
483-
case model.oglNorm16Ext && !useHalfFloat && VtkDataTypes.UNSIGNED_SHORT:
496+
case getNorm16Ext() && !useHalfFloat && VtkDataTypes.UNSIGNED_SHORT:
484497
return model.context.UNSIGNED_SHORT;
485498
// use half float type
486499
case useHalfFloat && VtkDataTypes.SHORT:
@@ -820,7 +833,7 @@ function vtkOpenGLTexture(publicAPI, model) {
820833
if (
821834
webGLInfo.RENDERER.value.match(/WebKit/gi) &&
822835
navigator.platform.match(/Mac/gi) &&
823-
model.oglNorm16Ext &&
836+
getNorm16Ext() &&
824837
(dataType === VtkDataTypes.UNSIGNED_SHORT ||
825838
dataType === VtkDataTypes.SHORT)
826839
) {
@@ -925,7 +938,7 @@ function vtkOpenGLTexture(publicAPI, model) {
925938
numComps *
926939
model._openGLRenderWindow.getDefaultTextureByteSize(
927940
dataType,
928-
model.oglNorm16Ext,
941+
getNorm16Ext(),
929942
publicAPI.useHalfFloat()
930943
);
931944
publicAPI.deactivate();
@@ -1049,7 +1062,7 @@ function vtkOpenGLTexture(publicAPI, model) {
10491062
numComps *
10501063
model._openGLRenderWindow.getDefaultTextureByteSize(
10511064
dataType,
1052-
model.oglNorm16Ext,
1065+
getNorm16Ext(),
10531066
publicAPI.useHalfFloat()
10541067
);
10551068
// generateMipmap must not be called here because we manually upload all levels
@@ -1138,7 +1151,7 @@ function vtkOpenGLTexture(publicAPI, model) {
11381151
model.components *
11391152
model._openGLRenderWindow.getDefaultTextureByteSize(
11401153
dataType,
1141-
model.oglNorm16Ext,
1154+
getNorm16Ext(),
11421155
publicAPI.useHalfFloat()
11431156
);
11441157

@@ -1248,7 +1261,7 @@ function vtkOpenGLTexture(publicAPI, model) {
12481261
model.components *
12491262
model._openGLRenderWindow.getDefaultTextureByteSize(
12501263
VtkDataTypes.UNSIGNED_CHAR,
1251-
model.oglNorm16Ext,
1264+
getNorm16Ext(),
12521265
publicAPI.useHalfFloat()
12531266
);
12541267

@@ -1401,11 +1414,7 @@ function vtkOpenGLTexture(publicAPI, model) {
14011414
}
14021415

14031416
// Handle SHORT data type with EXT_texture_norm16 extension
1404-
if (
1405-
model.oglNorm16Ext &&
1406-
!useHalfFloat &&
1407-
dataType === VtkDataTypes.SHORT
1408-
) {
1417+
if (getNorm16Ext() && !useHalfFloat && dataType === VtkDataTypes.SHORT) {
14091418
for (let c = 0; c < numComps; ++c) {
14101419
model.volumeInfo.scale[c] = 32767.0; // Scale to [-1, 1] range
14111420
}
@@ -1414,7 +1423,7 @@ function vtkOpenGLTexture(publicAPI, model) {
14141423

14151424
// Handle UNSIGNED_SHORT data type with EXT_texture_norm16 extension
14161425
if (
1417-
model.oglNorm16Ext &&
1426+
getNorm16Ext() &&
14181427
!useHalfFloat &&
14191428
dataType === VtkDataTypes.UNSIGNED_SHORT
14201429
) {
@@ -1569,7 +1578,7 @@ function vtkOpenGLTexture(publicAPI, model) {
15691578
model.components *
15701579
model._openGLRenderWindow.getDefaultTextureByteSize(
15711580
dataTypeToUse,
1572-
model.oglNorm16Ext,
1581+
getNorm16Ext(),
15731582
publicAPI.useHalfFloat()
15741583
);
15751584

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/**
2+
* Even when the EXT_texture_norm16 extension is present, linear filtering
3+
* might not be supported for normalized fixed point textures.
4+
*
5+
* This is a driver bug. See https://github.com/KhronosGroup/WebGL/issues/3706
6+
* @return {boolean}
7+
*/
8+
function supportsNorm16Linear() {
9+
try {
10+
const canvasSize = 4;
11+
const texWidth = 2;
12+
const texHeight = 1;
13+
const texData = new Int16Array([0, 2 ** 15 - 1]);
14+
const pixelToCheck = [1, 1];
15+
16+
const canvas = document.createElement('canvas');
17+
canvas.width = canvasSize;
18+
canvas.height = canvasSize;
19+
const gl = canvas.getContext('webgl2');
20+
if (!gl) {
21+
return false;
22+
}
23+
24+
const ext = gl.getExtension('EXT_texture_norm16');
25+
if (!ext) {
26+
return false;
27+
}
28+
29+
const vs = `#version 300 es
30+
void main() {
31+
gl_PointSize = ${canvasSize.toFixed(1)};
32+
gl_Position = vec4(0, 0, 0, 1);
33+
}
34+
`;
35+
const fs = `#version 300 es
36+
precision highp float;
37+
precision highp int;
38+
precision highp sampler2D;
39+
40+
uniform sampler2D u_image;
41+
42+
out vec4 color;
43+
44+
void main() {
45+
vec4 intColor = texture(u_image, gl_PointCoord.xy);
46+
color = vec4(vec3(intColor.rrr), 1);
47+
}
48+
`;
49+
50+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
51+
gl.shaderSource(vertexShader, vs);
52+
gl.compileShader(vertexShader);
53+
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
54+
return false;
55+
}
56+
57+
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
58+
gl.shaderSource(fragmentShader, fs);
59+
gl.compileShader(fragmentShader);
60+
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
61+
return false;
62+
}
63+
64+
const program = gl.createProgram();
65+
gl.attachShader(program, vertexShader);
66+
gl.attachShader(program, fragmentShader);
67+
gl.linkProgram(program);
68+
69+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
70+
return false;
71+
}
72+
73+
const tex = gl.createTexture();
74+
gl.bindTexture(gl.TEXTURE_2D, tex);
75+
gl.texImage2D(
76+
gl.TEXTURE_2D,
77+
0,
78+
ext.R16_SNORM_EXT,
79+
texWidth,
80+
texHeight,
81+
0,
82+
gl.RED,
83+
gl.SHORT,
84+
texData
85+
);
86+
87+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
88+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
89+
gl.useProgram(program);
90+
gl.drawArrays(gl.POINTS, 0, 1);
91+
92+
const pixel = new Uint8Array(4);
93+
gl.readPixels(
94+
pixelToCheck[0],
95+
pixelToCheck[1],
96+
1,
97+
1,
98+
gl.RGBA,
99+
gl.UNSIGNED_BYTE,
100+
pixel
101+
);
102+
const [r, g, b] = pixel;
103+
104+
const webglLoseContext = gl.getExtension('WEBGL_lose_context');
105+
if (webglLoseContext) {
106+
webglLoseContext.loseContext();
107+
}
108+
109+
return r === g && g === b && r !== 0;
110+
} catch (e) {
111+
return false;
112+
}
113+
}
114+
115+
/**
116+
* @type {boolean | undefined}
117+
*/
118+
let supportsNorm16LinearCache;
119+
120+
function supportsNorm16LinearCached() {
121+
// Only create a canvas+texture+shaders the first time
122+
if (supportsNorm16LinearCache === undefined) {
123+
supportsNorm16LinearCache = supportsNorm16Linear();
124+
}
125+
126+
return supportsNorm16LinearCache;
127+
}
128+
129+
export default supportsNorm16LinearCached;

0 commit comments

Comments
 (0)