Search code examples
webglmobile-safariframebuffer

Rendering to (or reading from?) framebuffer doesn't work in mobile Safari


For some reason, rendering to framebuffer and then from the buffer to screen doesn't seem to work on mobile Safari. I had no opportunity to check out desktop Safari, but my Firefox renders the Microsoft logo fine, on my phone I just see the border:

const dmnsn = [800, 600],
  glCtx = twgl.getContext(document.createElement("canvas")),
  imgLc =
    "https://upload.wikimedia.org/wikipedia/commons/b/b6/Microsoft_logo_%281987%29.svg";
var bfInf = twgl.primitives.createXYQuadBufferInfo(glCtx),
  pgInf = twgl.createProgramInfo(glCtx, ["vs", "fs"]),
  imgTx = twgl.createTexture(glCtx, { src: imgLc }, tstSB),
  scnBf = twgl.createFramebufferInfo(glCtx, [{ wrap: glCtx.REPEAT}],dmnsn[0],dmnsn[1]);

function plcCv() {
  document.body.append(glCtx.canvas);
  glCtx.canvas.width = dmnsn[0];
  glCtx.canvas.height = dmnsn[1];
}
function drwTx(tx, fbi, fy) {
  twgl.bindFramebufferInfo(glCtx, fbi);
  twgl.drawObjectList(glCtx, [
    {
      programInfo: pgInf,
      bufferInfo: bfInf,
      uniforms: { u_texture: tx, u_flipy: fy }
    }
  ]);
}
function tstSB() {
  plcCv();
  drwTx(imgTx, scnBf, true);
  drwTx(scnBf.attachments[0], null, false);
}
html{
  height:100%;
}
body{
  margin:0;
  height:100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
canvas{
  border-style:solid;
}
<script id="vs" type="x-shader/x-vertex">
  
  attribute vec4 position;
  attribute vec2 texcoord;
    
  uniform bool u_flipy;
  
  varying vec2 v_texcoord;

  void main() {
    if(u_flipy) {
      v_texcoord = vec2(texcoord.x, 1.0 - texcoord.y);
    } else {
      v_texcoord = texcoord;
    }
    gl_Position = position;
  }
</script>
<script id="fs" type="x-shader/x-fragment">
precision mediump float;

varying vec2 v_texcoord;

uniform sampler2D u_texture;

void main() {
  gl_FragColor=texture2D(u_texture,v_texcoord);
}
</script>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

I'm aware that drawing SVGs is somewhat experimental, but that seemed to work just fine also on mobile Safari.


Solution

  • Checking the errors in Safari I see

    WebGL: drawElements: texture bound to texture unit 0 is not renderable. It maybe non-power-of-2 and have incompatible texture filtering or is not 'texture complete', or it is a float/half-float type with linear filtering and without the relevant float/half-float linear extension enabled.

    To figure out which texture was the issue used webgl-lint and added these lines

      const ext = glCtx.getExtension('GMAN_debug_helper');
      const tagObject = ext ? ext.tagObject.bind(ext) : () => {};
      tagObject(imgTx, 'imgTx');
      tagObject(scnBf.attachments[0], 'colorAttach');
    

    Then I got this error

    error in drawElements(TRIANGLES, 6, UNSIGNED_SHORT, 0): texture WebGLTexture("colorAttach") on texture unit 0 referenced by uniform sampler2D u_texture is not renderable: texture's width(800) is not a power of 2 and height(600) is not a power of 2 but TEXTURE_WRAP_S (REPEAT) is not CLAMP_TO_EDGE and TEXTURE_WRAP_T (REPEAT) is not CLAMP_TO_EDGE. with WebGLProgram("unnamed") as current program with the default vertex array bound

    The issue is twgl.getContext returns WebGL2 on Chrome/Firefox but only WebGL1 on Safari (which doesn't yet support WebGL2)

    I added this webgl-helper to disable WebGL2 and I get the same error in Chrome.

    If you just want WebGL1 always then don't use twgl.getContext.

    Otherwise you can fix it changing the line for twgl.createFramebufferInfo to set WebGL1 compatible parameters

      scnBf = twgl.createFramebufferInfo(glCtx, [{ }],dmnsn[0],dmnsn[1]);
    

    IIRC the defaults in twgl for createFramebufferInfo are wrap: CLAMP_TO_EDGE, minMag: LINEAR

    const dmnsn = [800, 600],
      glCtx = twgl.getContext(document.createElement("canvas")),
      imgLc =
        "https://upload.wikimedia.org/wikipedia/commons/b/b6/Microsoft_logo_%281987%29.svg";
    var bfInf = twgl.primitives.createXYQuadBufferInfo(glCtx),
      pgInf = twgl.createProgramInfo(glCtx, ["vs", "fs"]),
      imgTx = twgl.createTexture(glCtx, { src: imgLc }, tstSB),
      scnBf = twgl.createFramebufferInfo(glCtx, [{ }],dmnsn[0],dmnsn[1]);
    
    function plcCv() {
      document.body.append(glCtx.canvas);
      glCtx.canvas.width = dmnsn[0];
      glCtx.canvas.height = dmnsn[1];
    }
    function drwTx(tx, fbi, fy) {
      twgl.bindFramebufferInfo(glCtx, fbi);
      twgl.drawObjectList(glCtx, [
        {
          programInfo: pgInf,
          bufferInfo: bfInf,
          uniforms: { u_texture: tx, u_flipy: fy }
        }
      ]);
    }
    function tstSB() {
      plcCv();
      drwTx(imgTx, scnBf, true);
      drwTx(scnBf.attachments[0], null, false);
    }
    html{
      height:100%;
    }
    body{
      margin:0;
      height:100%;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    canvas{
      border-style:solid;
    }
    <script id="vs" type="x-shader/x-vertex">
      
      attribute vec4 position;
      attribute vec2 texcoord;
        
      uniform bool u_flipy;
      
      varying vec2 v_texcoord;
    
      void main() {
        if(u_flipy) {
          v_texcoord = vec2(texcoord.x, 1.0 - texcoord.y);
        } else {
          v_texcoord = texcoord;
        }
        gl_Position = position;
      }
    </script>
    <script id="fs" type="x-shader/x-fragment">
    precision mediump float;
    
    varying vec2 v_texcoord;
    
    uniform sampler2D u_texture;
    
    void main() {
      gl_FragColor=texture2D(u_texture,v_texcoord);
    }
    </script>
    <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>