可以添加WebGL readPixels调用来更改着色器的输出吗?

时间:2020-05-10 10:36:39

标签: webgl

如果在程序中间插入了错误的readPixels命令(写入未使用的缓冲区),是否可以创建一系列WebGL命令以提供不同的输出?

上下文

我遇到了在WebGL中应用一系列着色器的情况,有时它们会计算错误的内容。事实证明,隔离bug非常困难,因为它似乎依赖于着色器的随机细节以及某些多余的调用的存在。据我所知,我并没有做错任何事情,甚至没有特别之处。我怀疑GPU驱动程序错误,带有某种竞态条件,因为它不一致以捕获不良结果需要进行多少次迭代,但是如果我知道readPixels行为存在时应与不存在时的行为匹配。

作为参考,这是在我的台式机(Windows,NVidia GeForce RTX 2060,AMD Ryzen 7 2700X)上重现该错误的代码。它不会在我拥有的其他机器上复制:

const gl = document.createElement('canvas').getContext('webgl');
const GL = WebGLRenderingContext;
{
    gl.getExtension('OES_texture_float');
    gl.getExtension('WEBGL_color_buffer_float');
    let positionBuffer = gl.createBuffer();
    let positions = new Float32Array([-1, +1, +1, +1, -1, -1, +1, -1]);
    gl.bindBuffer(GL.ARRAY_BUFFER, positionBuffer);
    gl.bufferData(GL.ARRAY_BUFFER, positions, GL.STATIC_DRAW);
    let indexBuffer = gl.createBuffer();
    let indices = new Uint16Array([0, 2, 1, 2, 3, 1]);
    gl.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(GL.ELEMENT_ARRAY_BUFFER, indices, GL.STATIC_DRAW);
    gl.viewport(0, 0, 4, 2);
}

function shader(fragmentShaderSource) {
    let glVertexShader = gl.createShader(GL.VERTEX_SHADER);
    gl.shaderSource(glVertexShader, `
            precision highp float;
            precision highp int;
            attribute vec2 position;
            void main() {
              gl_Position = vec4(position, 0, 1);
            }`);
    gl.compileShader(glVertexShader);

    let glFragmentShader = gl.createShader(GL.FRAGMENT_SHADER);
    gl.shaderSource(glFragmentShader, `
            precision highp float;
            precision highp int;
            ${fragmentShaderSource}`);
    gl.compileShader(glFragmentShader);

    let program = gl.createProgram();
    gl.attachShader(program, glVertexShader);
    gl.attachShader(program, glFragmentShader);
    gl.linkProgram(program);

    gl.deleteShader(glVertexShader);
    gl.deleteShader(glFragmentShader);
    return program;
}

function tex() {
    let texture = gl.createTexture();
    let framebuffer = gl.createFramebuffer();

    gl.bindTexture(GL.TEXTURE_2D, texture);
    gl.bindFramebuffer(GL.FRAMEBUFFER, framebuffer);
    gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
    gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
    gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
    gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
    gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, 4, 2, 0, GL.RGBA, GL.FLOAT, null);
    gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, texture, 0);

    return {texture, framebuffer};
}

let shader_less_than = shader(`
    uniform float lim;
    void main() {
        gl_FragColor = vec4(float(gl_FragCoord.y < lim), 0.0, 0.0, 0.0);
    }`);

let shader_zero = shader(`
    void main() {
        gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
    }`);

let shader_tricky = shader(`
    uniform sampler2D tex_unused_dep;
    uniform sampler2D tex_c;

    void main() {
        float c = texture2D(tex_c, gl_FragCoord.xy / vec2(4.0, 2.0)).x;
        vec2 unused = texture2D(tex_unused_dep, vec2(0.0, c)).xy; // Without this line, test passes.
        gl_FragColor = vec4(0.0, c, 0.0, 0.0);
    }`);

let tex_unrelated = tex();
let tex_lim = tex();
let tex_unused_dep = tex();
let tex_out = tex();
let out_buf = new Float32Array(32);

for (let k = 0; k < 1000; k++) {
    let flag = k % 2 === 0;

    gl.useProgram(shader_zero);
    gl.bindFramebuffer(GL.FRAMEBUFFER, tex_unused_dep.framebuffer);
    gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0);

    gl.useProgram(shader_less_than);
    gl.uniform1f(gl.getUniformLocation(shader_less_than, 'lim'), flag ? 1 : 2);
    gl.bindFramebuffer(GL.FRAMEBUFFER, tex_unrelated.framebuffer);
    gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0);

    gl.uniform1f(gl.getUniformLocation(shader_less_than, 'lim'), flag ? 1 : 2);  // Commenting this line makes a pass more likely, but not guaranteed.
    gl.bindFramebuffer(GL.FRAMEBUFFER, tex_lim.framebuffer);
    gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0);

    // gl.readPixels(0, 0, 4, 2, GL.RGBA, GL.FLOAT, out_buf); // Uncommenting this line seems to guarantee a pass.

    gl.useProgram(shader_tricky);
    gl.uniform1i(gl.getUniformLocation(shader_tricky, 'tex_unused_dep'), 0);
    gl.uniform1i(gl.getUniformLocation(shader_tricky, 'tex_c'), 1);
    gl.activeTexture(GL.TEXTURE0 + 0);
    gl.bindTexture(GL.TEXTURE_2D, tex_unused_dep.texture);
    gl.activeTexture(GL.TEXTURE0 + 1);
    gl.bindTexture(GL.TEXTURE_2D, tex_lim.texture);
    gl.enableVertexAttribArray(gl.getAttribLocation(shader_tricky, 'position'));
    gl.vertexAttribPointer(gl.getAttribLocation(shader_tricky, 'position'), 2, GL.FLOAT, false, 0, 0);
    gl.bindFramebuffer(GL.FRAMEBUFFER, tex_out.framebuffer);
    gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0);

    gl.readPixels(0, 0, 4, 2, GL.RGBA, GL.FLOAT, out_buf);
    if (out_buf[17] !== (flag ? 0 : 1)) {
        throw new Error("Bad output.")
    }
}
console.log("PASS");

0 个答案:

没有答案
相关问题