deck.gl icon indicating copy to clipboard operation
deck.gl copied to clipboard

[Feat] change ScatterplotLayer position by using fbo texture

Open ouzhou opened this issue 1 year ago • 5 comments

Target Use Case

add new feature read position from texture

Proposal

custom particle movement when size more than 1 million points my step 1 create a compute shader to caculate position 2 if the fbo parameter is passed , read positions from fbo

ouzhou avatar May 15 '24 07:05 ouzhou

I think you'll be better off implementing it as a custom layer.

You may also consider passing a Buffer to data.attributes.

Pessimistress avatar Jun 11 '24 17:06 Pessimistress

createBuffer is ok
createFramebuffer only show one dot at [0,0] position in canvas

      export const padData = new Float32Array([
           0, 0, 0, 0,
           10, 0, 0, 0,
           20, 0, 0, 0,
           30, 0, 0, 0,
           40, 0, 0, 0,
        ]);
        const len = 5;
        const texture = device.createTexture({
          id: "color-attachment",
          width: 1,
          height: len,
          data: padData,
          format: "rgba32float",
        });
 
        const fBuffer = device.createFramebuffer({
          id: "framebuffer",
          width: 1,
          height: len,
          colorAttachments: [texture],
        });

        const layers = [
          new ScatterplotLayer({
            id: "ScatterplotLayer",
            data: {
              length: len,
              attributes: {
                getPosition: {
                  buffer: fBuffer,
                  size: 2,
                  offset: 0,
                  stride: 4 * Float32Array.BYTES_PER_ELEMENT,
                  type: "float32",
                  offset: 0,
                },
              },
            },
            getFillColor: [255, 140, 255, 255],
            getLineColor: [0, 0, 0],
            getRadius: 1,
          }),
        ];

ouzhou avatar Jun 27 '24 03:06 ouzhou

Framebuffer is not a Buffer. You can copy the data from a texture to a Buffer on the GPU.

@ibgreen looks like luma.gl docs is missing CommandEncoder?

Pessimistress avatar Jun 27 '24 04:06 Pessimistress

I found this; it might be useful for my situation.(1 million particle gpgpu motion like d3-force) https://luma.gl/docs/api-guide/engine/transforms use BufferTransform instand, Transfrom deprecated in v9

@Pessimistress can you tell me how to trigger layer update when buffer change? use updateTriggers is a good practice ?

:( Performance is poor at 1 million points

 import { useMemo, useState } from "react";
import DeckGL from "@deck.gl/react";
import { OrthographicView } from "@deck.gl/core";
import { ScatterplotLayer } from "@deck.gl/layers";
import { BufferTransform } from "@luma.gl/engine";
import { Buffer } from "@luma.gl/core";
import type { Device } from "@luma.gl/core";

const positions = [];

function getRandomNumberInRange(min: number, max: number) {
  if (min > max) {
    [min, max] = [max, min];
  }
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

const len = 100 * 10000;
for (let i = 0; i < len; i++) {
  // positions.push([10 * i, 10 * i]);
  positions.push([
    getRandomNumberInRange(-1000, 1000),
    getRandomNumberInRange(-1000, 1000),
  ]);
}

const padData = new Float32Array(
  positions.flatMap((item) => {
    return [...item];
  })
);

class ForceLayout {
  prevPositionBuffer: Buffer;
  nextPositionBuffer: Buffer;
  transform: BufferTransform;

  constructor(device: Device) {
    this.prevPositionBuffer = device.createBuffer(padData);
    this.nextPositionBuffer = device.createBuffer(
      new Float32Array(padData.byteLength)
    );
    this.transform = new BufferTransform(device, {
      vs: `\
#version 300 es

in vec2 position;
out vec2 vPosition;

void main() {
    vPosition = 20.0 + position;
}
`,
      attributes: { position: this.prevPositionBuffer },
      feedbackBuffers: { vPosition: this.nextPositionBuffer },
      varyings: ["vPosition"],
      bufferLayout: [{ name: "position", format: "float32x2" }],
      vertexCount: len,
    });
  }

  onRender() {
    this.transform.run();

    this._swap();
  }

  protected _swap() {
    const prevPositionBuffer = this.nextPositionBuffer;
    const nextPositionBuffer = this.prevPositionBuffer;

    this.transform.model.setAttributes({ position: prevPositionBuffer });
    this.transform.transformFeedback.setBuffers({
      vPosition: nextPositionBuffer,
    });

    this.nextPositionBuffer = nextPositionBuffer;
    this.prevPositionBuffer = prevPositionBuffer;
  }
}

function App() {
  const width = window.innerWidth;
  const height = window.innerHeight;

  const [viewState, setViewState] = useState({
    target: [0, 0],
    zoom: 1,
  });

  const [layers, setLayers] = useState([]);

  const views = useMemo(() => {
    return [
      new OrthographicView({
        id: "main",
        flipY: false,
        width,
        height,
        controller: {
          doubleClickZoom: false,
          keyboard: false,
        },
      }),
    ];
  }, [height, width]);

  return (
    <DeckGL
      onDeviceInitialized={(device) => {
        const force = new ForceLayout(device);

        const a = new ScatterplotLayer({
          id: "ScatterplotLayer",
          data: {
            length: len,
            attributes: {
              getPosition: {
                buffer: force.prevPositionBuffer,
                size: 2,
                offset: 0,
                stride: 2 * Float32Array.BYTES_PER_ELEMENT,
              },
            },
          },
          getFillColor: [255, 140, 255, 255],
          getLineColor: [0, 0, 0],
          getRadius: 1,
          // updateTriggers: {
          //   getPosition: year
          // }
        });

        const layers = [a];
        setLayers(layers);

        // window.a = a;

        // setInterval(() => {
        //   console.log("2");
        //   force.onRender();
        //   a.internalState.needsRedraw = true;
        // }, 1000);

        requestAnimationFrame(function render() {
          force.onRender();
          if (a.internalState) {
            a.internalState.needsRedraw = true;
          }

          requestAnimationFrame(render);
        });
      }}
      views={views}
      layers={layers}
      viewState={viewState}
      onViewStateChange={({ viewState: v }) => setViewState(v)}
    />
  );
}

export default App;

ouzhou avatar Jun 29 '24 01:06 ouzhou

@ibgreen @Pessimistress Can you help me?

ouzhou avatar Sep 29 '24 08:09 ouzhou