Open hiukim opened 4 years ago
I have also tried with a simplest possible code gpu = new GPU()
, and doesn't work.
Still working to reproduce, fyi.
@robertleeplummerjr So I've been trying to dig into the code, and I've located some symptoms. The error throw around the getFeatures
method https://github.com/gpujs/gpu.js/blob/d16b5eca5bc4e784eeaabca94bfe1114e767433b/src/backend/gl/kernel.js#L115
I also dig deeper and found that the result of getIsFloatRead
is weird in my ipad safari. I managed to isolate a lot of code from getIsFloatRead
and create a small scripts that can reproduce the errors (this can be run entirely in an independent html file without gpu.js):
<script type="x-shader/x-vertex" id="vertex-shader">
precision lowp float;
precision lowp int;
precision lowp sampler2D;
attribute vec2 aPos;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
uniform vec2 ratio;
void main(void) {
gl_Position = vec4((aPos + vec2(1)) * ratio + vec2(-1), 0, 1);
vTexCoord = aTexCoord;
}
</script>
<script type="x-shader/x-fragment" id="fragment-shader">
precision lowp float;
precision lowp int;
precision lowp sampler2D;
ivec3 uOutputDim = ivec3(1, 1, 1);
ivec2 uTexSize = ivec2(1, 1);
varying vec2 vTexCoord;
int index;
float kernelResult;
void kernel() {
kernelResult = 3.0;
return;
}
void main(void) {
index = int(vTexCoord.s * float(uTexSize.x)) + int(vTexCoord.t * float(uTexSize.y)) * uTexSize.x;
kernel();
gl_FragData[0][0] = kernelResult;
}
</script>
<script>
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const testExtensions = {
OES_texture_float: gl.getExtension('OES_texture_float'),
OES_texture_float_linear: gl.getExtension('OES_texture_float_linear'),
OES_element_index_uint: gl.getExtension('OES_element_index_uint'),
WEBGL_draw_buffers: gl.getExtension('WEBGL_draw_buffers'),
};
const texSize = [1, 1];
const maxTexSize = [1, 1];
gl.enable(gl.SCISSOR_TEST);
var source = document.querySelector("#vertex-shader").innerHTML;
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader,source);
gl.compileShader(vertexShader);
source = document.querySelector("#fragment-shader").innerHTML
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader,source);
gl.compileShader(fragmentShader);
program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
const framebuffer = gl.createFramebuffer();
framebuffer.width = texSize[0];
framebuffer.height = texSize[1];
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
const vertices = new Float32Array([
-1, -1,
1, -1,
-1, 1,
1, 1
]);
const texCoords = new Float32Array([
0, 0,
1, 0,
0, 1,
1, 1
]);
const texCoordOffset = vertices.byteLength;
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices.byteLength + texCoords.byteLength, gl.STATIC_DRAW);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
const aPosLoc = gl.getAttribLocation(program, 'aPos');
gl.enableVertexAttribArray(aPosLoc);
gl.vertexAttribPointer(aPosLoc, 2, gl.FLOAT, false, 0, 0);
gl.useProgram(program);
const loc = gl.getUniformLocation(program, 'ratio')
gl.uniform2f(loc, texSize[0] / maxTexSize[0], texSize[1] / maxTexSize[1]);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
const w = 1;
const h = 1;
const result = new Uint8Array(w * h * 4);
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, result);
console.log("result 1", result);
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize[0], texSize[1], 0, gl.RGBA, gl.FLOAT, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, result);
console.log("result 2", result);
const formatValues = (array, width) => {
const xResults = new Float32Array(width);
let i = 0;
for (let x = 0; x < width; x++) {
xResults[x] = array[i];
i += 4;
}
return xResults;
};
const output = [1];
const result2 = new Float32Array(w * h * 4);
gl.readPixels(0, 0, w, h, gl.RGBA, gl.FLOAT, result2);
const buildResult = formatValues(
result2,
output[0],
output[1],
output[2]
);
console.log("result 3", buildResult);
</script>
In my desktop chrome browser, the output (correct output) is:
result 1 Uint8Array(4) [0, 0, 0, 0]
result 2 Uint8Array(4) [255, 0, 0, 0]
result 3 Float32Array [3]
whereas in my ipad pro safari:
result 1 Uint8Array(4) [0, 0, 0, 0]
result 2 Uint8Array(4) [0, 0, 0, 0]
result 3 Float32Array [0]
Unfortunately I'm pretty new to webgl, so I don't understand majority of the code and have a hard time going further. Maybe you could share some insighs?
I might have figured out the problem:
Let me give out the fix first before I go into more details about my findings: https://github.com/hiukim/gpu.js/commit/1c08d76d7b67ddfa9179f1430ffec50ed14f9ac7#diff-d0869ed9081a7d67554e369dca2c1e3dR50 It's not a good fix, as It's only suppressing the symptom. I can't be sure about the root cause, but I will give my findings and see if anyone have more information.
When startup, it checks the kernel feature. More specifically, there is a getIsFloatRead
method.
If I understand correctly, this method assume precision: single
is supported and then run the testing kernel and try whether the result matches.
Unfortunately, when precision
is set to single
. A float texture will be created down the road when building the kernel. i.e.
https://github.com/gpujs/gpu.js/blob/d16b5eca5bc4e784eeaabca94bfe1114e767433b/src/backend/web-gl/kernel.js#L718-L723 It tries to create a texture gl.FLOAT
instead of gl.UNSIGNED_BYTE
.
In my ipad pro safari, this is not successful. i.e.
gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
Normally, it should be fine. because this method is checking whether float is support, and apparently my ipad pro safari doesn't support. So all good.
Now comes the weird things. The code continues to run until the next method try to create kernel. It fails to compile the shader, so the error throws in this line:
I have tried to comment out the getIsFloatRead
method and there is no problem. So I can confirm that some side effect is created while running the getIsFloatRead
method that somehow crashed the gl context, making it fails to further compile shaders.
Then I dig further, and found that it might be related to the texture or framebuffer (but this part I'm not sure). Anyway, after some trial and error, I added a line kernel.context.bufferData(kernel.context.ARRAY_BUFFER, 0, kernel.context.STATIC_DRAW);
inside the getIsFloatRead
method, and the problem goes away. Code here:
It seems to be able to suppress the problem for now, but a better fix is probably required.
@hiukim Unfortunately when using your quick-fix, gpu.js falls back to CPU mode for me and then fails (at least in my use case)
@mauriceackel Now I simply return false
in the first line of getIsFloatRead
. You can try. It might affect accuracies or performances in some devices I guess.
What is wrong?
It's very weird. The example (https://observablehq.com/@fil/image-to-gpu) doesn't work at all on my ipad pro (3rd generation). i've just updated iOS version to 13.6, but not sure whether it's related. Have tried with both chrome and safari, both didn't work. I have tried other devices including android and iphone, no problem.
Attached is the screenshot showing the error:
render = Error: Error compiling vertex shader: null
How do we replicate the issue?
Just open the example page: https://observablehq.com/@fil/image-to-gpu
How important is this (1-5)?
5
Other Comments