pixijs icon indicating copy to clipboard operation
pixijs copied to clipboard

Feat/transform feedback

Open orange4glace opened this issue 2 years ago • 2 comments

I have enthusiasm to introduce GPUParticle to PixiJS world.

There're various ways to implement GPUParitlce and I choose to use TransformFeedback.

To do that, I've implemented TransformFeedbackSystem.

TransformFeedbackSystem manages stuffs related to TransformFeedback.

Then, we can make a GPUParticle with existing PIXI classes like Mesh, Shader.

class GPUParticle extends Container
{
    // Meshes to update Particle data
    particleUpdateMeshes: Mesh<Shader>[];
    // TransformFeedbacks
    transformFeedbacks: TransformFeedback[];
    // Meshes to render Particle data
    particleRenderMeshes: Mesh<Shader>[];

    // Flag for double buffering
    inputFlag = 0;

    particleUpdateShader: Shader;
    particleRenderShader: Shader;

    constructor()
    {
        this.particleUpdateShader = new Shader(vert1, frag1, 'ParticleUpdate', {
            transformFeedbackVaryings: {
                names: ['out_Position', 'out_Age'],
                bufferMode: 'separate'
            }
        });

        this.particleRenderShader = new Shader(vert2, frag2, 'ParticleRender');
        
        // Create two particleUpdateMeshes with particleUpdateShader
        this.particleUpdateMeshes.push(this.createParticleUpdateMesh());
        this.particleUpdateMeshes.push(this.createParticleUpdateMesh());
        
        // Create two particleRenderMeshes with particleRenderShader
        this.particleRenderMeshes.push(this.createParticleRenderMesh());
        this.particleRenderMeshes.push(this.createParticleRenderMesh());

        const transformFeedback1 = new TransformFeedback();
        transformFeedback.bindBuffer(0, particleUpdateMeshes[1].geometry.getBuffer('in_Position'));
        transformFeedback.bindBuffer(1, particleUpdateMeshes[1].geometry.getBuffer('in_Age'));
        transformFeedbacks.push(transformFeedback1);

        // Create transformFeedback2
        // ....

        protected _render(renderer: Renderer): void
        {
                const inputFlag = this.inputFlag;

                const updateMesh = this.particleUpdateMeshes[inputFlag];
                const renderMesh = this.particleRenderMeshes[inputFlag];
                const transformFeedback.= this.transformFeedbacks[inputFlag];

                renderer.gl.enable(renderer.gl.RASTERIZER_DISCARD);

                // Update Particle
                renderer.transformFeedback.bind(transformFeedback);
                renderer.transformFeedback.beginTransformFeedback(updateMesh.shader, DRAW_MODES.POINTS);
                updateMesh.render(renderer);
                renderer.transformFeedback.endTransformFeedback();
                renderer.transformFeedback.unbind();

                renderer.gl.disable(renderer.gl.RASTERIZER_DISCARD);

                // Render Particle
                renderMesh.render(renderer);

                this.inputFlag = inputFlag ? 0 : 1;
        }
}

Jul-14-2022 15-09-13

Description of change
Pre-Merge Checklist
  • [ ] Tests and/or benchmarks are included
  • [x] Documentation is changed or added
  • [x] Lint process passed (npm run lint)
  • [x] Tests passed (npm run test)

orange4glace avatar Jul 14 '22 06:07 orange4glace

can you add some example of usage of this new feature in documentation in this repo and/or in https://github.com/pixijs/examples ?

domis86 avatar Jul 14 '22 11:07 domis86

can you add some example of usage of this new feature in documentation in this repo and/or in https://github.com/pixijs/examples ?

Sure! I think it could be done in examples repo after this PR is merged.

orange4glace avatar Jul 14 '22 12:07 orange4glace

@orange4glace would love to get this in v7. Would you might resolving these conflicts?

bigtimebuddy avatar Oct 07 '22 14:10 bigtimebuddy

@orange4glace would love to get this in v7. Would you might resolving these conflicts?

Sure! just fixed. (new System implementations are also applied, like extensions.add(TransformFeedbackSystem);)

I've checked it works well with following example code,

<script src="./pixi.js"></script>


<body></body>
<script>
  
  const vert1 = `
  #version 300 es
  in vec4 aPos;
  void main(void) {
    gl_PointSize = 20.;
    gl_Position = vec4(aPos.x + 0.005, aPos.yzw);
  }
  `
  
  const frag1 = `
  #version 300 es
  precision mediump float;
  out vec4 fragColor;
  void main(void){
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  }
  `
  
  class GPUParticle extends PIXI.Container
  {
    // Meshes to update Particle data
    particleUpdateMeshes = [];
    // TransformFeedbacks
    transformFeedbacks = [];
    // Meshes to render Particle data
    particleRenderMeshes = [];
    
    // Flag for double buffering
    inputFlag = 0;
    
    particleUpdateShader;
    particleRenderShader;
    
    constructor()
    {
      super();
      this.particleUpdateShader = new PIXI.Program(vert1, frag1, 'ParticleUpdate', {
        transformFeedbackVaryings: {
          names: ['gl_Position'],
          bufferMode: 'separate'
        }
      });
      
      this.particleRenderShader = new PIXI.Program(vert1, frag1, 'ParticleRender');
      
      // Create two particleUpdateMeshes with particleUpdateShader
      this.particleUpdateMeshes.push(this.createParticleUpdateMesh());
      this.particleUpdateMeshes.push(this.createParticleUpdateMesh());
      
      // Create two particleRenderMeshes with particleRenderShader
      this.particleRenderMeshes.push(this.createParticleRenderMesh(this.particleUpdateMeshes[0].geometry.getBuffer('aPos')));
      this.particleRenderMeshes.push(this.createParticleRenderMesh(this.particleUpdateMeshes[1].geometry.getBuffer('aPos')));
      
      const transformFeedback1 = new PIXI.TransformFeedback();
      transformFeedback1.bindBuffer(0, this.particleUpdateMeshes[1].geometry.getBuffer('aPos'));
      this.transformFeedbacks.push(transformFeedback1);
      
      const transformFeedback2 = new PIXI.TransformFeedback();
      transformFeedback2.bindBuffer(0, this.particleUpdateMeshes[0].geometry.getBuffer('aPos'));
      this.transformFeedbacks.push(transformFeedback2);
    }
    
    createParticleUpdateMesh() {
      const geometry = new PIXI.Geometry()
      .addAttribute('aPos', [-1, 0, 0, 1]);
      const mesh = new PIXI.Mesh(geometry, new PIXI.Shader(this.particleUpdateShader), undefined, PIXI.DRAW_MODES.POINTS);
      
      return mesh;
    }
    
    createParticleRenderMesh(buffer) {
      const geometry = new PIXI.Geometry()
      .addAttribute('aPos', buffer);
      const mesh = new PIXI.Mesh(geometry, new PIXI.Shader(this.particleRenderShader), undefined, PIXI.DRAW_MODES.POINTS);
      
      return mesh;
    }
    
    _render(renderer)
    {
      const inputFlag = this.inputFlag;
      
      const updateMesh = this.particleUpdateMeshes[inputFlag];
      const renderMesh = this.particleRenderMeshes[inputFlag];
      const transformFeedback = this.transformFeedbacks[inputFlag];
      
      renderer.gl.enable(renderer.gl.RASTERIZER_DISCARD);
      
      // Update Particle
      renderer.transformFeedback.bind(transformFeedback);
      renderer.transformFeedback.beginTransformFeedback(PIXI.DRAW_MODES.POINTS, updateMesh.shader);
      updateMesh.render(renderer);
      
      renderer.transformFeedback.endTransformFeedback();
      renderer.transformFeedback.unbind();
      
      renderer.gl.disable(renderer.gl.RASTERIZER_DISCARD);
      
      // Render Particle
      renderMesh.render(renderer);
      
      this.inputFlag = inputFlag ? 0 : 1;
    }
  }
  
  
  // Create the application helper and add its render target to the page
  let app = new PIXI.Application({ width: 640, height: 360 });
  document.body.appendChild(app.view);
  
  const particle = new GPUParticle();
  app.stage.addChild(particle)
  
  // Add a ticker callback to move the sprite back and forth
  let elapsed = 0.0;
  app.ticker.add((delta) => {
    elapsed += delta;
  });
  
  
</script>

Oct-08-2022 01-55-58

orange4glace avatar Oct 07 '22 16:10 orange4glace