繁体   English   中英

如何使用WebGL提高性能?

[英]How to increase performance using WebGL?

我正在使用WebGL渲染纹理,但是,渲染的方式是渲染几行数据,然后向右移动这些行,然后再次绘制另一组线。

例如:我有一个640 * 480的图像,其中包含640 * 480 * 4像素的RGBA,但是我仅填充alpha值,它是GrayScale Medical Dicom图像。

现在,我面临的问题是用抽搐渲染纹理,图像渲染无法顺利进行。

例如,这就是发生的情况:要渲染640行数据。

因此,我使用了一个640 * 480 * 4的arraybuffer,然后,假设第一行通过websocket从服务器到达客户端以进行渲染,然后我将索引分别填充为3、640 + 3、640 * 2 + 3、640 * 3 +3,依此类推,直到640 * 480 + 3。 然后当收到第二行时,我将第一行移到第二行,例如3-> 7,640 + 3-> 640 + 7,...... 640 * 480 + 3-> 640 * 480 + 7。 然后,新接收到的行将被渲染为3、640 + 3、640 * 2 + 3、640 * 3 + 3,并且将持续到图像数据的第640行。

这是我完成的代码。

码:

    var renderLineData = function (imageAttr) {
    var data = imageAttr.data;
    var LINES_PER_CHUNK = imageAttr.lines;
    var alpha = 4;
    if(imageAttr.newImage) {
        newBuffer = new ArrayBuffer(imageAttr.width * imageAttr.height * alpha);dataTypedArray = new Uint8Array(newBuffer);
        // provide texture coordinates for the rectangle.
        provideTextureCoordsForRect();
        setParams();
        // Upload the image into the texture.
        // look up uniform locations
        uploadImageToTexture(gl.getUniformLocation(program, 'u_matrix'));
    } else {
        for (var z = imageAttr.index; z > 0; z--) {
            for (i = 0 ; i < LINES_PER_CHUNK; i++) {
                for (j = 0 ; j < imageAttr.height; j++) {
                    dataTypedArray[i * alpha + imageAttr.width*alpha * j + 3 + LINES_PER_CHUNK  * alpha * z] = dataTypedArray[i * alpha + imageAttr.width*alpha * j +  3 + LINES_PER_CHUNK  * alpha * (z-1)];
                }
            }
        }
    }
    for (i = 0, k = imageAttr.height*LINES_PER_CHUNK; i < LINES_PER_CHUNK; i++) {
        for (j = 0 ; j < imageAttr.height; j++) {
            dataTypedArray[i * alpha + imageAttr.width*4 * j + 3] = data[k - imageAttr.height + j];
        }
        k = k - imageAttr.height;
    }
    imageAttrTemp = imageAttr;
    renderImgSlowly(gl, imageAttr, dataTypedArray);
};
function renderImgSlowly (gl, image, dataTypedArray)  {
    gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, dataTypedArray);
    //Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
}

首先,您所做的任何事情都可能是速度问题。 640x320的图像不是很大,并且您在JavaScript中执行的处理量不太可能成为瓶颈。

最重要的是,WebGL可以毫不费力地绘制单个四边形,这就是您要绘制的全部。 上传640x480纹理也没有问题。

瓶颈是网络。 通过网络发送块很慢。

另一方面,如果要优化,为什么要在JavaScript中移动数据? 只需将其放置在纹理中的正确位置(以gl.texSubImage2D 如果只想绘制已放入数据的零件,则调整纹理坐标以选择该纹理的那一部分

另外,如果只需要一个通道,为什么还要使用RGBA 使用LUMINANCE

  if (imageAttr.newImage) {
      destColumn = imageAttr.width;
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, imageAttr.width, imageAttr.height, 0,
                    gl.LUMINANCE, gl.UNSIGNED_BYTE, null);
  }
  destColumn -= imageAttr.lines;
  // should check it destColumn does not go negative!
  gl.texSubImage2D(gl.TEXTURE_2D, 0, destColumn, 0, imageAttr.lines, imageAttr.height,
                   gl.LUMINANCE, gl.UNSIGNED_BYTE, imageAttr.data);

  var srcX = destColumn;
  var srcY = 0;
  var srcWidth  = imageAttr.width - destColumn;
  var srcHeight = imageAttr.height;

  var dstX = destColumn * gl.canvas.width / imageAttr.width;
  var dstY = 0;
  var dstWidth  = srcWidth * gl.canvas.width / imageAttr.width;
  var dstHeight = srcHeight;

  var texWidth     = imageAttr.width;
  var texHeight    = imageAttr.height;
  var targetWidth  = gl.canvas.width;
  var targetHeight = gl.canvas.height;

  drawImageInWebGL(
    tex, texWidth, texHeight,  
    srcX, srcY, srcWidth, srcHeight,
    dstX, dstY, dstWidth, dstHeight,
    targetWidth, targetHeight);
}

这是一个例子

 var m4 = twgl.m4; var gl = document.getElementById("c").getContext("webgl"); // compiles shader, links and looks up locations var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]); // a unit quad var arrays = { position: { numComponents: 2, data: [ 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, ], }, }; // calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays); // we're only using 1 texture so just make and bind it now var tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); 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.NEAREST); var destColumn = 0; // We're using 1 byte wide texture pieces so we need to // set UNPACK_ALIGNMENT to 1 as it defaults to 4 gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); simulateSendingAnImageNColumnsAtATime(1, 1, addLinesToImageAndDraw); function addLinesToImageAndDraw(imageAttr) { if (imageAttr.newImage) { destColumn = imageAttr.width; gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, imageAttr.width, imageAttr.height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, null); } destColumn -= imageAttr.lines; // should check it destColumn does not go negative! gl.texSubImage2D(gl.TEXTURE_2D, 0, destColumn, 0, imageAttr.lines, imageAttr.height, gl.LUMINANCE, gl.UNSIGNED_BYTE, imageAttr.data); var srcX = destColumn; var srcY = 0; var srcWidth = imageAttr.width - destColumn; var srcHeight = imageAttr.height; var dstX = destColumn * gl.canvas.width / imageAttr.width; var dstY = 0; var dstWidth = srcWidth * gl.canvas.width / imageAttr.width; var dstHeight = gl.canvas.height; var texWidth = imageAttr.width; var texHeight = imageAttr.height; var targetWidth = gl.canvas.width; var targetHeight = gl.canvas.height; drawImage( tex, texWidth, texHeight, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, targetWidth, targetHeight); } // we pass in texWidth and texHeight because unlike images // we can't look up the width and height of a texture // we pass in targetWidth and targetHeight to tell it // the size of the thing we're drawing too. We could look // up the size of the canvas with gl.canvas.width and // gl.canvas.height but maybe we want to draw to a framebuffer // etc.. so might as well pass those in. // srcX, srcY, srcWidth, srcHeight are in pixels // computed from texWidth and texHeight // dstX, dstY, dstWidth, dstHeight are in pixels // computed from targetWidth and targetHeight function drawImage( tex, texWidth, texHeight, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, targetWidth, targetHeight) { var mat = m4.identity(); var tmat = m4.identity(); var uniforms = { matrix: mat, textureMatrix: tmat, texture: tex, }; // these adjust the unit quad to generate texture coordinates // to select part of the src texture // NOTE: no check is done that srcX + srcWidth go outside of the // texture or are in range in any way. Same for srcY + srcHeight m4.translate(tmat, [srcX / texWidth, srcY / texHeight, 0], tmat); m4.scale(tmat, [srcWidth / texWidth, srcHeight / texHeight, 1], tmat); // these convert from pixels to clip space m4.ortho(0, targetWidth, targetHeight, 0, -1, 1, mat) // these move and scale the unit quad into the size we want // in the target as pixels m4.translate(mat, [dstX, dstY, 0], mat); m4.scale(mat, [dstWidth, dstHeight, 1], mat); gl.useProgram(programInfo.program); // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo); // calls gl.uniformXXX, gl.activeTexture, gl.bindTexture twgl.setUniforms(programInfo, uniforms); // calls gl.drawArray or gl.drawElements twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo); } // ===================================================================== // Everything below this line represents stuff from the server. // so it's irrelevant to the answer // function simulateSendingAnImageNColumnsAtATime(minColumnsPerChunk, maxColumnsPerChunk, callback) { var imageData = createImageToSend(640, 480); // cut data into columns at start because this work would be done on // the server var columns = []; var x = 0; while (x < imageData.width) { // how many columns are left? var maxWidth = imageData.width - x; // how many columns should we send var columnWidth = Math.min(maxWidth, rand(minColumnsPerChunk, maxColumnsPerChunk + 1)); var data = createImageChunk(imageData, imageData.width - x - columnWidth, 0, columnWidth, imageData.height); columns.push({ newImage: x === 0, lines: columnWidth, width: imageData.width, height: imageData.height, data: data, }); x += columnWidth; } var columnNdx = 0; sendNextColumn(); function sendNextColumn() { if (columnNdx < columns.length) { callback(columns[columnNdx++]); if (columnNdx < columns.length) { // should we make this random to siumlate network speed var timeToNextChunkMS = 17; setTimeout(sendNextColumn, timeToNextChunkMS); } } } } function createImageChunk(imageData, x, y, width, height) { var data = new Uint8Array(width * height); for (var yy = 0; yy < height; ++yy) { for (var xx = 0; xx < width; ++xx) { var srcOffset = ((yy + y) * imageData.width + xx + x) * 4; var dstOffset = yy * width + xx; // compute gray scale var gray = Math.max(imageData.data[srcOffset], imageData.data[srcOffset + 1], imageData.data[srcOffset + 2]); data[dstOffset] = gray; } } return data; } function rand(min, max) { return Math.floor(Math.random() * max - min) + min; } function createImageToSend(width, height) { // create a texture using a canvas so we don't have to download one var ctx = document.createElement("canvas").getContext("2d"); ctx.width = width; ctx.height = height; ctx.fillStyle = "#222"; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.lineWidth = 20; ["#AAA", "#888", "#666"].forEach(function(color, ndx, array) { ctx.strokeStyle = color; ctx.beginPath(); ctx.arc((ndx + 1) / (array.length + 1) * ctx.canvas.width, ctx.canvas.height / 2, ctx.canvas.height * 0.4, 0, Math.PI * 2, false); ctx.stroke(); }); ctx.fillStyle = "white"; ctx.font = "40px sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("Some Image", ctx.canvas.width / 2, ctx.canvas.height / 2); return ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); } 
 canvas { border: 1px solid black; } 
 <script src="https://twgljs.org/dist/twgl-full.min.js"></script> <script id="vs" type="not-js"> // we will always pass a 0 to 1 unit quad // and then use matrices to manipulate it attribute vec4 position; uniform mat4 matrix; uniform mat4 textureMatrix; varying vec2 texcoord; void main () { gl_Position = matrix * position; texcoord = (textureMatrix * position).xy; } </script> <script id="fs" type="not-js"> precision mediump float; varying vec2 texcoord; uniform sampler2D texture; void main() { gl_FragColor = texture2D(texture, texcoord); } </script> <canvas id="c" width="640" height="480"></canvas> 

注意:这不会很顺利,因为它使用setTimeout模拟接收网络数据,但这正是您可能看到的内容。

这是一个独立于更新纹理而旋转图像的示例。 您可以看到它运行得非常顺利。 缓慢不是WebGL,而是网络(如setTimeout模拟)

 var m4 = twgl.m4; var gl = document.getElementById("c").getContext("webgl"); // compiles shader, links and looks up locations var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]); // a unit quad var arrays = { position: { numComponents: 2, data: [ 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, ], }, }; // calls gl.createBuffer, gl.bindBuffer, gl.bufferData for each array var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays); // we're only using 1 texture so just make and bind it now var tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); 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.NEAREST); var destColumn = 0; var imageWidth; var imageHeight; // We're using 1 byte wide texture pieces so we need to // set UNPACK_ALIGNMENT to 1 as it defaults to 4 gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); simulateSendingAnImageNColumnsAtATime(1, 1, addLinesToImageAndDraw); function addLinesToImageAndDraw(imageAttr) { if (imageAttr.newImage) { destColumn = imageAttr.width; imageWidth = imageAttr.width; imageHeight = imageAttr.height; gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, imageAttr.width, imageAttr.height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, null); } destColumn -= imageAttr.lines; // should check it destColumn does not go negative! gl.texSubImage2D(gl.TEXTURE_2D, 0, destColumn, 0, imageAttr.lines, imageAttr.height, gl.LUMINANCE, gl.UNSIGNED_BYTE, imageAttr.data); } function render(time) { if (imageWidth) { var srcX = destColumn; var srcY = 0; var srcWidth = imageWidth - destColumn; var srcHeight = imageHeight; var dstX = destColumn * gl.canvas.width / imageWidth; var dstY = 0; var dstWidth = srcWidth * gl.canvas.width / imageWidth; var dstHeight = gl.canvas.height; var texWidth = imageWidth; var texHeight = imageHeight; var targetWidth = gl.canvas.width; var targetHeight = gl.canvas.height; drawImageWithRotation( time * 0.001, tex, texWidth, texHeight, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, targetWidth, targetHeight); } requestAnimationFrame(render); } requestAnimationFrame(render); // we pass in texWidth and texHeight because unlike images // we can't look up the width and height of a texture // we pass in targetWidth and targetHeight to tell it // the size of the thing we're drawing too. We could look // up the size of the canvas with gl.canvas.width and // gl.canvas.height but maybe we want to draw to a framebuffer // etc.. so might as well pass those in. // srcX, srcY, srcWidth, srcHeight are in pixels // computed from texWidth and texHeight // dstX, dstY, dstWidth, dstHeight are in pixels // computed from targetWidth and targetHeight function drawImageWithRotation( rotation, tex, texWidth, texHeight, srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, targetWidth, targetHeight) { var mat = m4.identity(); var tmat = m4.identity(); var uniforms = { matrix: mat, textureMatrix: tmat, texture: tex, }; // these adjust the unit quad to generate texture coordinates // to select part of the src texture // NOTE: no check is done that srcX + srcWidth go outside of the // texture or are in range in any way. Same for srcY + srcHeight m4.translate(tmat, [srcX / texWidth, srcY / texHeight, 0], tmat); m4.scale(tmat, [srcWidth / texWidth, srcHeight / texHeight, 1], tmat); // convert from pixels to clipspace m4.ortho(0, targetWidth, targetHeight, 0, -1, 1, mat); // rotate around center of canvas m4.translate(mat, [targetWidth / 2, targetHeight / 2, 0], mat); m4.rotateZ(mat, rotation, mat); m4.translate(mat, [-targetWidth / 2, -targetHeight / 2, 0], mat); // these move and scale the unit quad into the size we want // in the target as pixels m4.translate(mat, [dstX, dstY, 0], mat); m4.scale(mat, [dstWidth, dstHeight, 1], mat); gl.useProgram(programInfo.program); // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo); // calls gl.uniformXXX, gl.activeTexture, gl.bindTexture twgl.setUniforms(programInfo, uniforms); // calls gl.drawArray or gl.drawElements twgl.drawBufferInfo(gl, bufferInfo); } // ===================================================================== // Everything below this line represents stuff from the server. // so it's irrelevant to the answer // function simulateSendingAnImageNColumnsAtATime(minColumnsPerChunk, maxColumnsPerChunk, callback) { var imageData = createImageToSend(640, 480); // cut data into columns at start because this work would be done on // the server var columns = []; var x = 0; while (x < imageData.width) { // how many columns are left? var maxWidth = imageData.width - x; // how many columns should we send var columnWidth = Math.min(maxWidth, rand(minColumnsPerChunk, maxColumnsPerChunk + 1)); var data = createImageChunk(imageData, imageData.width - x - columnWidth, 0, columnWidth, imageData.height); columns.push({ newImage: x === 0, lines: columnWidth, width: imageData.width, height: imageData.height, data: data, }); x += columnWidth; } var columnNdx = 0; sendNextColumn(); function sendNextColumn() { if (columnNdx < columns.length) { callback(columns[columnNdx++]); if (columnNdx < columns.length) { // should we make this random to siumlate network speed var timeToNextChunkMS = 17; setTimeout(sendNextColumn, timeToNextChunkMS); } } } } function createImageChunk(imageData, x, y, width, height) { var data = new Uint8Array(width * height); for (var yy = 0; yy < height; ++yy) { for (var xx = 0; xx < width; ++xx) { var srcOffset = ((yy + y) * imageData.width + xx + x) * 4; var dstOffset = yy * width + xx; // compute gray scale var gray = Math.max(imageData.data[srcOffset], imageData.data[srcOffset + 1], imageData.data[srcOffset + 2]); data[dstOffset] = gray; } } return data; } function rand(min, max) { return Math.floor(Math.random() * max - min) + min; } function createImageToSend(width, height) { // create a texture using a canvas so we don't have to download one var ctx = document.createElement("canvas").getContext("2d"); ctx.width = width; ctx.height = height; ctx.fillStyle = "#222"; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.lineWidth = 20; ["#AAA", "#888", "#666"].forEach(function(color, ndx, array) { ctx.strokeStyle = color; ctx.beginPath(); ctx.arc((ndx + 1) / (array.length + 1) * ctx.canvas.width, ctx.canvas.height / 2, ctx.canvas.height * 0.4, 0, Math.PI * 2, false); ctx.stroke(); }); ctx.fillStyle = "white"; ctx.font = "40px sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("Some Image", ctx.canvas.width / 2, ctx.canvas.height / 2); return ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); } 
 canvas { border: 1px solid black; } 
 <script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script> <script id="vs" type="not-js"> // we will always pass a 0 to 1 unit quad // and then use matrices to manipulate it attribute vec4 position; uniform mat4 matrix; uniform mat4 textureMatrix; varying vec2 texcoord; void main () { gl_Position = matrix * position; texcoord = (textureMatrix * position).xy; } </script> <script id="fs" type="not-js"> precision mediump float; varying vec2 texcoord; uniform sampler2D texture; void main() { gl_FragColor = texture2D(texture, texcoord); } </script> <canvas id="c" width="640" height="480"></canvas> 

gl.texImage2D速度很慢,在此方面没有太多可以改善的事情。 原因是texImage2D涉及状态更改,并要求GPU停止所有渲染,然后从CPU RAM中获取数据。 根据硬件的不同,主板和GPU之间的接口可能非常慢(与RAM访问速度相比)

您还会增加图像分辨率的问题。 GPU上的所有图像的高度和宽度大小均为2的幂(32,64,128,256,512,1024 ...)。 发送640 x 480的图像不符合此规则。 为了适应较小的尺寸,GPU将分配W 1024 x H 512像素的图像,因此必须重新调整图像数据的尺寸以适合内部尺寸(很快,因为这不是他们擅长的事情)。 根据硬件的不同,这将导致本来已经很慢的数据传输速度进一步降低。

如果使数据缓冲区等于两个规则(POT)(1024、512)的幂,则可能会有所改善。

最好的选择是在整个图像加载完毕之前避免传输,然后仅执行一次。

如果您确实需要它,那么我建议您将图像分成较小的图像,然后将较小的图像发送出去。 例如,对于POT图像,可以将1024 x 512的大小分为128 x 64的图像或1024 x 8的图像,从而得到64个较小的图像。 仅在较小的图像可用时发送它们,并且在渲染期间在GPU上将这些图像重新组合为一个。 这将使您将图像发送到GPU所需的时间几乎提高了64倍。

除此之外,还没有其他可以做的事情。 GPU擅长渲染,涉及主板IO时GPU很烂,在渲染过程中不惜一切代价避免这种情况,以充分利用图形硬件。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM