Avery
Avery

Reputation: 57

WebGL: read from a texture and write the output to a second texture

I am using WebGL2. I have two programs. Program 1 renders my favorite triangle, in my favorite color, into a texture, for safe keeping.

Program 2 reads the output of program 1 (the first texture) and then renders the contents of that texture.

This works fine when program 2 renders to the canvas, but I would like program 2 to render to a second texture. So after the first program's draw call I unbind the first fbo, create another fbo backed by a second texture, and then draw.

....

gl.bindFramebuffer(gl.FRAMEBUFFER,null)

gl.useProgram(program2)

....

const fbo2 = gl.createFramebuffer()
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)

const intermediateTexture = createEmptyTexture(gl,gl.canvas.width,gl.canvas.height,mipLevel)
gl.framebufferTexture2D(
    gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, intermediateTexture, mipLevel)

gl.viewport(0,0,gl.canvas.width,gl.canvas.height)
gl.drawArrays(gl.TRIANGLES, 0, 6)

This produces the following error: :GL_INVALID_OPERATION : glDrawArrays: Source and destination textures of the draw are the same.

My interpretation of this error is that program 2 is reading from, and writing to, the same texture. I think the problem is that I haven't told program 2 which texture to read from. But I don't know how to tell program 2 to read from the texture in fb1, after that frame buffer has been unbound.

How do I get program 2 to read from the first texture and draw to the second texture?

Thank you so much for your help

full code:

const vs1 = `#version 300 es

in vec4 a_position;

void main() {
    gl_Position = a_position;
}
`

const fs1 = `#version 300 es

precision highp float;

out vec4 outColor;

void main() {
    outColor = vec4(1, 0.5, .2, 1);
}
`
const vs2 = `#version 300 es

in vec3 a_texCoord;
out vec2 v_texCoord;

void main() {
    v_texCoord = a_texCoord.xy;
    gl_Position = vec4(a_texCoord, 1.0);
}`

const fs2 = `#version 300 es

precision highp float;

uniform sampler2D tex;
in vec2 v_texCoord;
out vec4 color;

void main() {
    color = texture(tex,v_texCoord);
}
`

function createShader(gl, type, source) {
    let shader = gl.createShader(type)
    gl.shaderSource(shader, source)
    gl.compileShader(shader)
    return shader
}

function createProgram(gl, vertexShader, fragmentShader) {
    let program = gl.createProgram()
    gl.attachShader(program, vertexShader)
    gl.attachShader(program, fragmentShader)
    gl.linkProgram(program)
    return program
}

function createEmptyTexture(gl, targetTextureWidth, targetTextureHeight, mipLevel) {
    let texture = gl.createTexture()
    gl.bindTexture(gl.TEXTURE_2D, texture)
    gl.texImage2D(gl.TEXTURE_2D, mipLevel, gl.RGB,
        targetTextureWidth, targetTextureHeight, 0,
        gl.RGB, gl.UNSIGNED_BYTE, null)
    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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
    return texture
}

function main() {
    const vertexShader1 = createShader(gl, gl.VERTEX_SHADER, vs1),
        fragmentShader1 = createShader(gl, gl.FRAGMENT_SHADER, fs1),

        vertexShader2 = createShader(gl, gl.VERTEX_SHADER, vs2),
        fragmentShader2 = createShader(gl, gl.FRAGMENT_SHADER, fs2),

        program1 = createProgram(gl, vertexShader1, fragmentShader1),
        program2 = createProgram(gl, vertexShader2, fragmentShader2),

        positionAttributeLocation = gl.getAttribLocation(program1, "a_position"),
        texturePositionAttributeLocation = gl.getAttribLocation(program2, "a_texCoord"),

        positionBuffer = gl.createBuffer()

    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, .5, 0, 0, 1, .5]), gl.STATIC_DRAW)

    const vao1 = gl.createVertexArray()

    gl.bindVertexArray(vao1)
    gl.enableVertexAttribArray(positionAttributeLocation)
    gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0)

    const mipLevel = 0

    // program1 draws to fbo1 backed by intermediateTexture at color attachment 0

    gl.useProgram(program1)
    gl.bindVertexArray(vao1)

    const fbo1 = gl.createFramebuffer()
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo1)
    const intermediateTexture = createEmptyTexture(gl, gl.canvas.width, gl.canvas.height, mipLevel)

    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, intermediateTexture, mipLevel)
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
    gl.drawArrays(gl.TRIANGLES, 0, 3)

    gl.bindFramebuffer(gl.FRAMEBUFFER, null) // Is this the problem?

    // program2 is supposed to draw to fbo2 backed by new texture at color attachment 0

    gl.useProgram(program2)

    const vao2 = gl.createVertexArray()
    gl.bindVertexArray(vao2)
    gl.enableVertexAttribArray(texturePositionAttributeLocation)
    gl.vertexAttribPointer(texturePositionAttributeLocation, 2, gl.FLOAT, false, 0, 0)
    gl.bindVertexArray(vao2)

    const quad = [-1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1]
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quad), gl.STATIC_DRAW)
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)

    const fbo2 = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2);
    const targetTexture = createEmptyTexture(gl, gl.canvas.width, gl.canvas.height, mipLevel)
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture, mipLevel)

    gl.drawArrays(gl.TRIANGLES, 0, 6) // error: glDrawArrays: Source and destination textures of the draw are the same
}

main();

Upvotes: 2

Views: 527

Answers (1)

LJᛃ
LJᛃ

Reputation: 8143

Your function createEmptyTexture binds the texture it creates to the (implicitly) activated texture unit 0, and leaves it bound. So if you want to read from the texture of the first framebuffer you'll need to bind that before rendering with program 2. That being said, usually you'd setup all the vertex-, index- and framebuffers with their respective textures and programs upfront during initialization, your render loop then emits bind, draw and attribute pointer calls.

Upvotes: 3

Related Questions