>

병렬 처리를 실행하기위한 WebGL에는 많은 추상화가 있습니다. 예 :

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

그러나 WebGL의 일반 GLSL 코드에서 단순하고 완전한 병렬 처리 예제가 어떤지 이해하는 데 어려움을 겪고 있습니다. WebGL에 대한 경험이 많지 않지만 조각 및 정점 셰이더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 개가있는 경우 순차적으로 10 회 병렬로 1,000 회 수행합니다.
  • 또는 원하는 병렬 처리량을 수동으로 지정해야하는 경우
  • 병렬 처리가 조각 셰이더 나 정점 셰이더 또는 둘 다에 적용되는 경우
  • 병렬 예제를 실제로 구현하는 방법

    • 답변 # 1

      먼저 WebGL은 점, 선 및 삼각형 만 래스터 화합니다. WebGL을 사용하여 비 래스터 화 (GPGPU)를 수행하는 것은 기본적으로 WebGL에 대한 입력이 배열 및 출력의 데이터임을 인식하는 문제입니다. 픽셀의 2D 사각형도 실제로는 그래픽이 아닌 데이터를 제공하고 창의적으로 래스터 화함으로써 2D 배열입니다. 그 데이터는 그래픽이 아닌 수학을 할 수 있습니다.

      WebGL은 두 가지 방식으로 병렬입니다.

      <올>

      CPU는 다른 프로세서 인 GPU에서 실행되고 있지만 CPU는 다른 작업을 수행 할 수있는 자유를 계산합니다

      GPU 자체는 병렬로 계산됩니다. 100 픽셀의 삼각형을 래스터 화하면 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 채널 텍스처를 사용하는 위의 예입니다. WebGL이 작업 당 4 개의 값을 처리 할 수 ​​있으므로 8 비트 1 채널 텍스처 (수학이 적음)를 사용하는 것이 더 쉽지만 효율성도 떨어집니다.

      8 비트 텍스처를 사용하기 때문에 0에서 255까지의 정수 값만 저장할 수 있습니다. 텍스처를 32 비트 부동 소수점 텍스처로 전환 할 수 있습니다. 부동 소수점 텍스처는 WebGL의 선택적 기능입니다 (확장을 활성화하고 성공했는지 확인해야 함). 부동 소수점 텍스처로 래스터 화하는 것도 옵션 기능입니다. 2018 년 현재 대부분의 모바일 GPU는 부동 소수점 텍스처로의 렌더링을 지원하지 않으므로 결과가 GPU에서 작동하도록하려면 결과를 지원하는 형식으로 창의적으로 인코딩하는 방법을 찾아야합니다.

      소스 데이터를 어드레싱하려면 수학이 1d 인덱스에서 2d 텍스처 좌표로 변환해야합니다. 위의 예에서 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);
      
      

      WebGL2

      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 가지 값으로 작동하게하면 실제로 계산을 계산하기가 복잡하거나 소스 데이터를 더 잘 일치시키기 위해 다시 정렬해야 할 수도 있습니다. 예를 들어 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 텍스처를 제공하지만 렌더링 할 수 있는지 여부는 WebGL2에서 R8 텍스처로 렌더링하는 것이 필수 기능인 선택적 기능입니다.

      WebGL2는 "변환 피드백"이라는 것을 제공합니다. 버텍스 쉐이더의 출력을 버퍼에 쓸 수 있습니다. 처리하려는 정점 수를 설정하는 것만으로도 이점이 있습니다 (대상 데이터가 사각형 일 필요는 없음). 또한 부동 소수점 값을 출력 할 수 있음을 의미합니다 (텍스처에 렌더링 할 때와 같이 선택 사항은 아님). 나는 (테스트하지는 않았지만) 텍스쳐로 렌더링하는 것보다 느리다고 믿습니다.

      WebGL을 처음 사용하기 때문에이 튜토리얼을 제안 할 수 있습니다.

    관련 자료

  • 이전 vue.js - [vue 경고] - 지시어 정렬 가능 업데이트 후크 오류 : "정렬 가능 : el은 [object undefined]가 아닌 htmlelement 여야합니다"
  • 다음 visual studio - C # Windows 양식 응용 프로그램의 배수 함수에서 변수 사용