Search code examples
javascriptwebglwechat

WeChat (WebGL) MiniGame not working on a device, but works in devtool


What I want to do

I'm trying to port my own WebGL-based engine to WeChat MiniGame environment, and currently trying just to have WebGL context that will be cleared with pink color:

WeChat Developer Tool - WebGL works

What's the issue

I've followed examples that Tencent provides as well as ThreeJS example on how to setup game project. It works great within WeChat Developer Tool (as seen in the above image), however when I try to open it on my device (Android phone), it is stuck at 100% loading screen:

Stuck in loading minigame

It stays like this for about 1 minute, and then shows black screen.

My code

There's no resource loading in my code. Here is what is in my main.js:


var ctx = canvas.getContext('webgl', {
  antialias: true,
  depth: true,
  preserveDrawingBuffer: true
});

ctx.viewport(0,0,ctx.canvas.width,ctx.canvas.height)
ctx.colorMask(true,true,true,true)
ctx.depthMask(true)
ctx.enable(ctx.BLEND)
ctx.blendFunc(ctx.SRC_ALPHA, ctx.ONE_MINUS_SRC_ALPHA)
ctx.clearColor(1.0,0.0,1.0,1.0)

export default class Main {

  constructor() {
    window.requestAnimationFrame(this.loop.bind(this), canvas)
  }

  render() {
    ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT)
  }

  update() {

  }

  loop() {
    this.update()
    this.render()
    window.requestAnimationFrame(this.loop.bind(this), canvas)
  }
}

My game.js is also simple:

import './weapp-adapter/index.js'
import './symbol'
import Main from './js/main'
new Main()

My game.json only contains following:

{
    "deviceOrientation": "portrait"
}

Additional info

I've also noticed that when I try Threejs example (which works on the device) and comment lines in render function, it will then behave the same (get stuck in 100% loading).


Solution

  • Solution

    I've finally figured out how to solve it:

    When I've placed WebGL context initialization in very first call of animation frame, while actual rendering is done in all other calls, it worked as expected on my Android device. Here is main.js I've changed:

    export default class Main {
    
      constructor() {
        this.render = this.render_first
        requestAnimationFrame(() => this.animate())
      }
    
      showmsg(t,c) {
        wx.showModal({
          title: ""+t,
          content: ""+c,
          showCancel: false,
          confirmText:'OK',
          success: function(res){}
        });
      }
    
      animate() {
        this.render();
        requestAnimationFrame(() => this.animate())
      }
    
      render_first() {
    
        this.render = this.render_normal
        var _this = this
        this.domElement = canvas
    
        var contextAttributes = {
          alpha: false,
          depth: true,
          stencil: false,
          antialias: false
        }
    
        this.domElement.addEventListener("webglcontextlost", function(e){
          _this.showmsg("WebGL","Context lost");
        }, false)
    
        this.domElement.addEventListener("webglcontextrestored", function(e){
          _this.showmsg("WebGL","Context restored");
        }, false)
    
        this._gl = this.domElement.getContext( 'webgl', contextAttributes ) || this.domElement.getContext( 'experimental-webgl', contextAttributes )
    
        var _gl = this._gl
    
        var vsrc = ""
        vsrc += "uniform mat4 uModelView;"
        vsrc += "uniform mat4 uProjView;"
        vsrc += "attribute highp vec4 aPosition;"
        vsrc += "void main(void) {"
        vsrc += "   gl_Position = ( uProjView * uModelView ) * aPosition;"
        vsrc += "}"
    
        var vid = _gl.createShader(_gl.VERTEX_SHADER)
        _gl.shaderSource(vid,vsrc)
        _gl.compileShader(vid)
        if (!_gl.getShaderParameter(vid, _gl.COMPILE_STATUS)) {
            console.error("Vertex shader failed: ", _gl.getShaderInfoLog(vid))
        }
    
        this._vid = vid
    
        var fsrc = ""
        fsrc += "void main(void) {"
        fsrc += "   gl_FragColor = vec4(1.0,1.0,0.0,1.0);"
        fsrc += "}"
    
        var fid = _gl.createShader(_gl.FRAGMENT_SHADER)
        _gl.shaderSource(fid,fsrc)
        _gl.compileShader(fid)
        if (!_gl.getShaderParameter(fid, _gl.COMPILE_STATUS)) {
          console.error("Fragment shader failed: ", _gl.getShaderInfoLog(fid))
        }
    
        this._fid = fid
    
        var pid = _gl.createProgram()
        _gl.attachShader(pid,vid)
        _gl.attachShader(pid,fid)
        _gl.linkProgram(pid)
    
        if (!_gl.getProgramParameter(pid, _gl.LINK_STATUS)) {
            let info = _gl.getProgramInfoLog(pid)
            console.error("Program link failed:", info )
        }
    
        _gl.useProgram(pid)
    
        this._pid = pid
    
        var aPosition = _gl.getAttribLocation(pid,"aPosition")
        var uModelView = _gl.getUniformLocation(pid,"uModelView")
        var uProjView = _gl.getUniformLocation(pid,"uProjView")
    
        _gl.uniformMatrix4fv( uModelView, false, [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] )
        _gl.uniformMatrix4fv( uProjView, false, [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] )
    
        this.uni = [uModelView, uProjView]
    
        this.phase = 0.0
    
        var data = [0,0,0,1,0,0,0,1,0]
        var idata = [0,1,2]
    
        var vbID = _gl.createBuffer()
        _gl.bindBuffer(_gl.ARRAY_BUFFER,vbID)
        _gl.bufferData(_gl.ARRAY_BUFFER, new Float32Array(data), _gl.STATIC_DRAW)
    
        var vbiID = _gl.createBuffer();
        _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, vbiID)
        _gl.bufferData(_gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(idata), _gl.STATIC_DRAW)
    
        _gl.vertexAttribPointer( aPosition, 3, _gl.FLOAT, false, 0, 0 )
        _gl.enableVertexAttribArray( aPosition )
        _gl.bindBuffer(_gl.ELEMENT_ARRAY_BUFFER, vbiID)
    
        this.vb = [vbID,vbiID]
    
        _gl.clearColor(1.0,0.0,1.0,1.0)
      }
    
      render_normal() {
    
        var _gl = this._gl
    
        var et = 60.0 / 1000.0
        this.phase += 180.0 * 60.0 / 1000.0
        var py = Math.sin(this.phase * Math.PI/180.0) * 0.5
        _gl.uniformMatrix4fv( this.uni[0], false, [1,0,0,0,0,1,0,0,0,0,1,0,py,0,0,1] )
    
        _gl.clear( _gl.COLOR_BUFFER_BIT )
        _gl.drawElements(_gl.TRIANGLES, 3, _gl.UNSIGNED_SHORT, 0)
    
      }
    }
    
    

    What was the problem

    It seems that on an actual device, WeChat minigame runs animation loop in a separate thread than main JavaScript execution. Since WebGL (OpenGL) context is accessible only within the same thread (except in native apps where multiple threads can share same context), it would crash on the device since render function would attempt to access gl context that was initialized in a different thread.

    This wasn't visible in WeChat Developer Tools, since that tool does not simulate exactly how device architecture work, and animation frame and JavaScript execution seems happen in the same thread.