[Feat] change ScatterplotLayer position by using fbo texture
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
I think you'll be better off implementing it as a custom layer.
You may also consider passing a Buffer to data.attributes.
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,
}),
];
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?
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;
@ibgreen @Pessimistress Can you help me?