gpt4 book ai didi

webgl - WebGL 并行性的 Hello world 示例

转载 作者:行者123 更新时间:2023-12-04 20:29:13 26 4
gpt4 key购买 nike

似乎有许多围绕 WebGL 的抽象来运行并行处理,例如:

  • https://github.com/MaiaVictor/WebMonkeys
  • https://github.com/gpujs/gpu.js
  • https://github.com/turbo/js

  • 但是我很难理解一个简单而完整的并行示例在 WebGL 的普通 GLSL 代码中会是什么样子。我对 WebGL 没有太多经验,但我知道有 fragment and vertex shaders以及如何从 JavaScript 将它们加载到 WebGL 上下文中。我不知道如何使用着色器或哪个应该进行并行处理。

    我想知道是否可以演示一个简单的 hello world 示例 并行添加操作 ,本质上这是使用 GLSL/WebGL 着色器的并行形式/但是应该完成。
    var array = []
    var size = 10000
    while(size--) array.push(0)

    for (var i = 0, n = 10000; i < n; i++) {
    array[i] += 10
    }

    我想我基本上不明白:
  • 如果 WebGL 自动并行运行所有内容。
  • 或者,如果并行运行的事物数量达到最大值,那么如果您有 10,000 个事物,但只有 1000 个并行,那么它会按顺序并行执行 1,000 个 10 次。
  • 或者,如果您必须手动指定所需的并行量。
  • 如果并行性进入片段着色器或顶点着色器,或两者。
  • 如何实际实现并行示例。
  • 最佳答案

    首先,WebGL only rasterizes points, lines, and triangles .使用 WebGL 进行非光栅化 ( GPGPU ) 基本上是意识到 WebGL 的输入是来自数组和输出的数据,像素的 2D 矩形实际上也只是一个 2D 数组,因此通过创造性地提供非图形数据和创造性地光栅化这些数据,你可以做非图形数学。

    WebGL 以两种方式并行。

  • 它运行在不同的处理器 GPU 上,同时它在计算一些你的 CPU 可以自由做其他事情的东西。
  • GPU 本身是并行计算的。一个很好的例子,如果你光栅化一个 100 像素的三角形,GPU 可以并行处理每个像素,直到该 GPU 的限制。如果不深入挖掘,看起来 NVidia 1080 GPU 有 2560 个内核,因此假设它们不是专门的,并且假设其中一个可以并行计算 2560 个事物的最佳情况。

  • 举个例子,所有 WebGL 应用程序都使用上面第 (1) 和 (2) 点的并行处理,而没有做任何特殊的事情。

    尽管就地添加 10 到 10000 个元素并不是 WebGL 擅长的,因为 WebGL 无法在一次操作中读取和写入相同的数据。换句话说,您的示例需要是
    const size = 10000;
    const srcArray = [];
    const dstArray = [];
    for (let i = 0; i < size; ++i) {
    srcArray[i] = 0;
    }

    for (var i = 0, i < size; ++i) {
    dstArray[i] = srcArray[i] + 10;
    }

    就像任何编程语言一样,实现这一目标的方法不止一种。最快的可能是将所有值复制到一个纹理中,然后栅格化到另一个纹理中,从第一个纹理向上查找并将 +10 写入目的地。但是,有一个问题。将数据传入和传出 GPU 的速度很慢,因此您需要权衡在 GPU 上工作是否成功。

    另一个就像限制一样,你不能从同一个数组中读取和写入,你也不能随机访问目标数组。 GPU 正在光栅化一条线、点或三角形。它在绘制三角形时速度最快,但这意味着它决定以什么顺序写入哪些像素,因此您的问题也必须忍受这些限制。您可以使用指向作为随机选择目的地的一种方式,但渲染点比渲染三角形慢得多。

    请注意,“Compute Shaders”(还不是 WebGL 的一部分)为 GPU 添加了随机访问写入能力。

    例子:

    const gl = document.createElement("canvas").getContext("webgl");

    const vs = `
    attribute vec4 position;
    attribute vec2 texcoord;

    varying vec2 v_texcoord;

    void main() {
    gl_Position = position;
    v_texcoord = texcoord;
    }
    `;

    const fs = `
    precision highp float;
    uniform sampler2D u_srcData;
    uniform float u_add;

    varying vec2 v_texcoord;

    void main() {
    vec4 value = texture2D(u_srcData, v_texcoord);

    // We can't choose the destination here.
    // It has already been decided by however
    // we asked WebGL to rasterize.
    gl_FragColor = value + u_add;
    }
    `;

    // calls gl.createShader, gl.shaderSource,
    // gl.compileShader, gl.createProgram,
    // gl.attachShaders, gl.linkProgram,
    // gl.getAttributeLocation, gl.getUniformLocation
    const programInfo = twgl.createProgramInfo(gl, [vs, fs]);


    const size = 10000;
    // Uint8Array values default to 0
    const srcData = new Uint8Array(size);
    // let's use slight more interesting numbers
    for (let i = 0; i < size; ++i) {
    srcData[i] = i % 200;
    }

    // Put that data in a texture. NOTE: Textures
    // are (generally) 2 dimensional and have a limit
    // on their dimensions. That means you can't make
    // a 1000000 by 1 texture. Most GPUs limit from
    // between 2048 to 16384.
    // In our case we're doing 10000 so we could use
    // a 100x100 texture. Except that WebGL can
    // process 4 values at a time (red, green, blue, alpha)
    // so a 50x50 will give us 10000 values
    const srcTex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, srcTex);
    const level = 0;
    const width = Math.sqrt(size / 4);
    if (width % 1 !== 0) {
    // we need some other technique to fit
    // our data into a texture.
    alert('size does not have integer square root');
    }
    const height = width;
    const border = 0;
    const internalFormat = gl.RGBA;
    const format = gl.RGBA;
    const type = gl.UNSIGNED_BYTE;
    gl.texImage2D(
    gl.TEXTURE_2D, level, internalFormat,
    width, height, border, format, type, srcData);
    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_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

    // create a destination texture
    const dstTex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, dstTex);
    gl.texImage2D(
    gl.TEXTURE_2D, level, internalFormat,
    width, height, border, format, type, 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_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

    // make a framebuffer so we can render to the
    // destination texture
    const fb = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    // and attach the destination texture
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, dstTex, level);

    // calls gl.createBuffer, gl.bindBuffer, gl.bufferData
    // to put a 2 unit quad (2 triangles) into
    // a buffer with matching texture coords
    // to process the entire quad
    const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
    data: [
    -1, -1,
    1, -1,
    -1, 1,
    -1, 1,
    1, -1,
    1, 1,
    ],
    numComponents: 2,
    },
    texcoord: [
    0, 0,
    1, 0,
    0, 1,
    0, 1,
    1, 0,
    1, 1,
    ],
    });

    gl.useProgram(programInfo.program);

    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

    // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
    twgl.setUniforms(programInfo, {
    u_add: 10 / 255, // because we're using Uint8
    u_srcData: srcTex,
    });

    // set the viewport to match the destination size
    gl.viewport(0, 0, width, height);

    // draw the quad (2 triangles)
    const offset = 0;
    const numVertices = 6;
    gl.drawArrays(gl.TRIANGLES, offset, numVertices);

    // pull out the result
    const dstData = new Uint8Array(size);
    gl.readPixels(0, 0, width, height, format, type, dstData);

    console.log(dstData);
    <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>


    制作一个通用的数学处理器需要大量的工作。

    问题:

    纹理是 2D 数组,WebGL 只光栅化点、线和三角形,因此例如处理适合矩形的数据比不适合的数据容易得多。换句话说,如果您有 10001 个值,则没有适合整数个单位的矩形。最好填充您的数据并忽略最后的部分。换句话说,100x101 纹理将是 10100 个值。所以只需忽略最后 99 个值。

    上面的示例使用 8 位 4 channel 纹理。使用 8 位 1 channel 纹理会更容易(数学较少),但效率也较低,因为 WebGL 每次操作可以处理 4 个值。

    因为它使用 8 位纹理,所以它只能存储从 0 到 255 的整数值。我们可以将纹理切换为 32 位浮点纹理。浮点纹理是两个 WebGL 的可选功能(您需要启用扩展并检查它们是否成功)。光栅化为浮点纹理也是一个可选功能。截至 2018 年,大多数移动 GPU 不支持渲染为浮点纹理,因此如果您希望代码在这些 GPU 上运行,您必须找到创造性的方法将结果编码为它们支持的格式。

    寻址源数据需要数学运算才能从一维索引转换为二维纹理坐标。在上面的示例中,由于我们直接从 srcData 转换为 dstData 1 到 1,因此不需要数学运算。如果你需要跳过 srcData 你需要提供数学

    WebGL1
    vec2 texcoordFromIndex(int ndx) {
    int column = int(mod(float(ndx),float(widthOfTexture)));
    int row = ndx / widthOfTexture;
    return (vec2(column, row) + 0.5) / vec2(widthOfTexture, heighOfTexture);
    }

    vec2 texcoord = texcoordFromIndex(someIndex);
    vec4 value = texture2D(someTexture, texcoord);

    网页GL2
    ivec2 texcoordFromIndex(someIndex) {
    int column = ndx % widthOfTexture;
    int row = ndx / widthOfTexture;
    return ivec2(column, row);
    }

    int level = 0;
    ivec2 texcoord = texcoordFromIndex(someIndex);
    vec4 value = texelFetch(someTexture, texcoord, level);

    假设我们要对每 2 个数字求和。我们可能会做这样的事情

    const gl = document.createElement("canvas").getContext("webgl2");

    const vs = `
    #version 300 es
    in vec4 position;

    void main() {
    gl_Position = position;
    }
    `;

    const fs = `
    #version 300 es
    precision highp float;
    uniform sampler2D u_srcData;

    uniform ivec2 u_destSize; // x = width, y = height

    out vec4 outColor;

    ivec2 texcoordFromIndex(int ndx, ivec2 size) {
    int column = ndx % size.x;
    int row = ndx / size.x;
    return ivec2(column, row);
    }

    void main() {
    // compute index of destination
    ivec2 dstPixel = ivec2(gl_FragCoord.xy);
    int dstNdx = dstPixel.y * u_destSize.x + dstPixel.x;

    ivec2 srcSize = textureSize(u_srcData, 0);

    int srcNdx = dstNdx * 2;
    ivec2 uv1 = texcoordFromIndex(srcNdx, srcSize);
    ivec2 uv2 = texcoordFromIndex(srcNdx + 1, srcSize);

    float value1 = texelFetch(u_srcData, uv1, 0).r;
    float value2 = texelFetch(u_srcData, uv2, 0).r;

    outColor = vec4(value1 + value2);
    }
    `;

    // calls gl.createShader, gl.shaderSource,
    // gl.compileShader, gl.createProgram,
    // gl.attachShaders, gl.linkProgram,
    // gl.getAttributeLocation, gl.getUniformLocation
    const programInfo = twgl.createProgramInfo(gl, [vs, fs]);


    const size = 10000;
    // Uint8Array values default to 0
    const srcData = new Uint8Array(size);
    // let's use slight more interesting numbers
    for (let i = 0; i < size; ++i) {
    srcData[i] = i % 99;
    }

    const srcTex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, srcTex);
    const level = 0;
    const srcWidth = Math.sqrt(size / 4);
    if (srcWidth % 1 !== 0) {
    // we need some other technique to fit
    // our data into a texture.
    alert('size does not have integer square root');
    }
    const srcHeight = srcWidth;
    const border = 0;
    const internalFormat = gl.R8;
    const format = gl.RED;
    const type = gl.UNSIGNED_BYTE;
    gl.texImage2D(
    gl.TEXTURE_2D, level, internalFormat,
    srcWidth, srcHeight, border, format, type, srcData);
    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_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

    // create a destination texture
    const dstTex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, dstTex);
    const dstWidth = srcWidth;
    const dstHeight = srcHeight / 2;
    // should check srcHeight is evenly
    // divisible by 2
    gl.texImage2D(
    gl.TEXTURE_2D, level, internalFormat,
    dstWidth, dstHeight, border, format, type, 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_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

    // make a framebuffer so we can render to the
    // destination texture
    const fb = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    // and attach the destination texture
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, dstTex, level);

    // calls gl.createBuffer, gl.bindBuffer, gl.bufferData
    // to put a 2 unit quad (2 triangles) into
    // a buffer
    const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
    data: [
    -1, -1,
    1, -1,
    -1, 1,
    -1, 1,
    1, -1,
    1, 1,
    ],
    numComponents: 2,
    },
    });

    gl.useProgram(programInfo.program);

    // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
    twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

    // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
    twgl.setUniforms(programInfo, {
    u_srcData: srcTex,
    u_srcSize: [srcWidth, srcHeight],
    u_dstSize: [dstWidth, dstHeight],
    });

    // set the viewport to match the destination size
    gl.viewport(0, 0, dstWidth, dstHeight);

    // draw the quad (2 triangles)
    const offset = 0;
    const numVertices = 6;
    gl.drawArrays(gl.TRIANGLES, offset, numVertices);

    // pull out the result
    const dstData = new Uint8Array(size / 2);
    gl.readPixels(0, 0, dstWidth, dstHeight, format, type, dstData);

    console.log(dstData);
    <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>


    请注意,上面的示例使用 WebGL2。为什么?因为 WebGL2 支持渲染到 R8 格式的纹理,这使得数学变得容易。每个像素一个值,而不是像前面的例子那样每个像素 4 个值。当然,这也意味着它更慢,但让它使用 4 个值会使计算索引的数学变得非常复杂,或者可能需要重新排列源数据以更好地匹配。例如,不是值(value)指数 0, 1, 2, 3, 4, 5, 6, 7, 8, ...如果排列 0, 2, 4, 6, 1, 3, 5, 7, 8 ....,每 2 个值求和会更容易这样一次拉出 4 个并添加下一组 4 个值。另一种方法是使用 2 个源纹理,将所有偶数索引值放在一个纹理中,将奇数索引值放在另一个纹理中。

    WebGL1 提供 LUMINANCE 和 ALPHA 纹理,它们也是一个 channel ,但是否可以渲染它们是一项可选功能,而在 WebGL2 中,渲染到 R8 纹理是必需的功能。

    WebGL2 还提供了一种叫做“转换反馈”的东西。这使您可以将顶点着色器的输出写入缓冲区。它的优点是您只需设置要处理的顶点数(无需将目标数据设为矩形)。这也意味着您可以输出浮点值(它不是可选的,就像渲染到纹理一样)。我相信(虽然我没有测试过)它比渲染到纹理要慢。

    由于您是 WebGL 的新手,我建议您 these tutorials .

    关于webgl - WebGL 并行性的 Hello world 示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50013385/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com