1

使用 WebGL2,我将 4K x 2K 立体视频作为纹理流式传输到球体内部,以提供 360° VR 视频播放能力。考虑到按时返回,我已经优化了尽可能多的代码库,并且应用程序在使用 .H264 视频源时运行完美。

然而; 当使用 8 位 VP8 或 VP9(提供卓越的保真度和文件大小,AV1 对我不可用)时,由于解码 VP8/VP9 视频需要额外的 CPU 要求,我在较弱的系统上遇到 FPS 下降。

在分析应用程序时,我发现texSubImage2D更新视频纹理的每帧调用消耗了每一帧的大部分(texImage2D由于它的分配而更糟),但我不确定如何进一步优化它的使用。以下是我已经在做的事情,以尽量减少它的影响:

我在初始加载时使用 texStorage2D 缓存纹理的内存空间,以使其尽可能连续。

    let glTexture = gl.createTexture();
    let pixelData = new Uint8Array(4096*2048*3);
    pixelData.fill(255);

    gl.bindTexture(GL.TEXTURE_2D, glTexture);
    gl.texStorage2D(GL.TEXTURE_2D, 1, GL.RGB8, 4096, 2048);
    gl.texSubImage2D(GL.TEXTURE_2D, 0, 0, 0, 4096, 2048, GL.RGB, GL.RGB, pixelData);
    gl.generateMipmap(GL.TEXTURE_2D);

然后,在我的渲染循环中,处理每个对象的左右眼姿势,然后再移动到下一个对象。gl.bindTexture这使我只需要gl.texSubImage2D每帧每个对象调用一次。此外,我还跳过填充着色器程序定义了该实体的材质是否与前一个实体的材质相同、视频是否暂停或仍在加载。

/* Main Render Loop Extract */

//Called each frame after pre-sorting entities
function DrawScene(glLayer, pose, scene){
    //Entities are pre-sorted for transparency blending, rendering opaque first, and transparent second.
    for (let ii = 0; ii < _opaqueEntities.length; ii++){

        //Only render if entity and it's parent chain are active
        if(_opaqueEntities[ii] && _opaqueEntities[ii].isActiveHeirachy){

            for (let i = 0; i < pose.views.length; i++) {
                _RenderEntityView(pose, i, _opaqueEntities[ii]);
            }
        }
    }

    for (let ii = 0; ii < _transparentEntities.length; ii++) {
    
        //Only render if entity and it's parent chain are active
        if(_transparentEntities[ii] && _transparentEntities[ii].isActiveHeirachy){
        
            for (let i = 0; i < pose.views.length; i++) {           
                _RenderEntityView(pose, i, _transparentEntities[ii]);
            }
        }
    }
}

let _programData;
function _RenderEntityView(pose, viewIdx, entity){
    //Calculates/manipualtes view matrix for entity for this view. (<0.1ms)
    //...
     
     //Store reference to make stack overflow lines shorter :-)
    _programData = entity.material.shaderProgram;

    _BindEntityBuffers(entity, _programData);//The buffers Thomas, mind the BUFFERS!!!

    
    gl.uniformMatrix4fv(
        _programData.uniformData.uProjectionMatrix,
        false,
        _view.projectionMatrix
    );
    gl.uniformMatrix4fv(
        _programData.uniformData.uModelViewMatrix,
        false,
        _modelViewMatrix
    );

    //Render all triangles that make up the object.
    gl.drawElements(GL.TRIANGLES, entity.tris.length, GL.UNSIGNED_SHORT, 0);    
}

let _attrName;
let _attrLoc;
let textureData;
function _BindEntityBuffers(entity, programData){
    gl.useProgram(programData.program);
    
    //Binds pre-defined shader atributes on an as needed basis
    for(_attrName in programData.attributeData){
        _attrLoc = programData.attributeData[_attrName];

        //Bind only if exists in shader
        if(_attrLoc.key >= 0){
            _BindShaderAttributes(_attrLoc.key, entity.attrBufferData[_attrName].buffer,
                entity.attrBufferData[_attrName].compCount);
        }
    }
    
    //Bind triangle index buffer
    gl.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, entity.triBuffer);
    
    //If already in use, is instanced material so skip configuration.
    if(_materialInUse == entity.material){return;}
    _materialInUse = entity.material;
        
        
    //Use the material by applying it's specific uniforms
    //Apply base color
    gl.uniform4fv(programData.uniformData.uColor, entity.material.color);
    
    //If shader uses a difuse texture
    if(programData.uniformData.uDiffuseSampler){
        //Store reference to make stack overflow lines shorter :-)
        textureData = entity.material.diffuseTexture;
        
        gl.activeTexture(gl.TEXTURE0);
        
        //Use assigned texture
        gl.bindTexture(gl.TEXTURE_2D, textureData);
        
        //If this is a video, update the texture buffer using the current video's playback frame data
        if(textureData.type == TEXTURE_TYPE.VIDEO &&
            textureData.isLoaded &&
            !textureData.paused){
            
            //This accounts for 42% of all script execution time!!!
            gl.texSubImage2D(gl.TEXTURE_2D, textureData.level, 0, 0,
                textureData.width, textureData.height, textureData.internalFormat,
                textureData.srcType, textureData.video);
        }
        
        gl.uniform1i(programData.uniformData.uDiffuseSampler, 0);
    }   
}

function _BindShaderAttributes(attrKey, buffer, compCount, type=GL.FLOAT, normalize=false, stride=0, offset=0){
    gl.bindBuffer(GL.ARRAY_BUFFER, buffer);
    gl.vertexAttribPointer(attrKey, compCount, type, normalize, stride, offset);
    gl.enableVertexAttribArray(attrKey);
}

我已经考虑对所有 for 循环使用预定义的计数器来避免 var i=0; 分配,但从中获得的收益似乎不值得付出努力。

旁注,源视频实际上大于 4K,但任何高于 4K 和 FPS 的视频都会研磨到大约 10-12。

强制性:上面的关键功能是从我编写的一个更大的 WebGL 渲染框架中提取的,它本身已经运行得非常快。我不“只使用”Three、AFrame 或其他此类通用库的原因是它们没有来自 DOD 的 ATO,而内部开发的代码是可以的。

21 年9 月 9 日更新:当 chrome 从 90 更新到 93 时,texSubImage2D 的 WebGL 性能急剧下降,导致每帧执行 +100 毫秒,无论 CPU/GPU 能力如何。现在更改为使用 texImage2D 会导致每帧大约 16 毫秒。此外,从 RGB 转换到 RGB565 提供了几毫秒的性能,同时最低限度地牺牲了颜色。

我仍然很想听听 GL/WebGL 专家关于我还能做些什么来提高性能。

4

0 回答 0