Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a WebGPU based Gradient Heatmap Plugin #2225

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/blocks/src/gpu-multipass/.block
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
license: apache-2.0
29 changes: 29 additions & 0 deletions examples/blocks/src/gpu-multipass/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" src="./script.js"></script>
<title>Document</title>
<style>
#container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}

#canvas {
display: block;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="container">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
212 changes: 212 additions & 0 deletions examples/blocks/src/gpu-multipass/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
function configCanvas() {
const { width, height } = canvas.getBoundingClientRect();

canvas.width = width;
canvas.height = height;
}

function getPresentationFormat() {
return navigator.gpu.getPreferredCanvasFormat();
}

function getCanvasAndContext(device) {
const canvas = document.getElementById("canvas");

console.log(canvas.getBoundingClientRect());
const { width, height } = canvas.getBoundingClientRect();
console.log(width, height);
canvas.width = width * devicePixelRatio;
canvas.height = height * devicePixelRatio;

const ctx = canvas.getContext("webgpu");

ctx.configure({
device,
format: getPresentationFormat(),
alphaMode: "premultiplied",
});

return { canvas, ctx };
}

async function getAdapter() {
return await navigator.gpu.requestAdapter({
powerPreference: "high-performance",
});
}

async function getDevice(adapter) {
return await adapter.requestDevice();
}

function createShaderSrc() {
const shaderSrc = `
struct VertexInput {
@location(0) position: vec2<f32>,
@location(1) color: vec4<f32>,
}

struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
}

@vertex
fn vert_main(
vsIn: VertexInput,
) -> VertexOutput {
var output: VertexOutput;

output.position = vec4<f32>(vsIn.position, 0., 1.);
output.color = vsIn.color;

return output;
}

@fragment
fn frag_main(vsOut: VertexOutput) -> @location(0) vec4<f32> {
return vsOut.color;
}
`;

return shaderSrc;
}

async function createPipeline(device) {
const code = createShaderSrc();
const module = device.createShaderModule({ code });
const pipeline = device.createRenderPipeline({
layout: "auto",
vertex: {
module,
entryPoint: "vert_main",
buffers: [
{
arrayStride: 6 * 4, // 8 floats per vertex (x, y, r, g, b, a)
attributes: [
{
// position
shaderLocation: 0,
offset: 0,
format: "float32x2",
},
{
// color
shaderLocation: 1,
offset: 2 * 4, // 4 floats per vertex, position is 4 floats
format: "float32x4",
},
],
},
],
},
fragment: {
module,
entryPoint: "frag_main",
targets: [
{
format: getPresentationFormat(),
},
],
},
primitive: {
topology: "triangle-list",
},
});

return pipeline;
}

// prettier-ignore
const vertices = [
// triangle 1, red
[0.0 , 0.6, 1.0, 0.0, 0.0, 1.0,
0.3, -0.3, 1.0, 0.0, 0.0, 1.0,
-0.3, -0.3, 1.0, 0.0, 0.0, 1.0, ],

// triangle 2, green
[-0.3, 0.3, 0.0, 1.0, 0.0, 1.0,
0.3, -0.4, 0.0, 1.0, 0.0, 1.0,
0.7, 0.3, 0.0, 1.0, 0.0, 1.0, ],

// triangle 3, blue
[-0.3, 0.7, 0.0, 0.0, 1.0, 1.0,
0.3, -0.6, 0.0, 0.0, 1.0, 1.0,
-0.7, 0.7, 0.0, 0.0, 1.0, 1.0,]
];

const createVertexBuffer = (device) => (vertsArr) => {
const verts = new Float32Array(vertsArr);
const vertexBuffer = device.createBuffer({
size: verts.byteLength,
usage: GPUBufferUsage.VERTEX,
mappedAtCreation: true,
});

new Float32Array(vertexBuffer.getMappedRange()).set(verts);
vertexBuffer.unmap();

return vertexBuffer;
};

function createVertexBuffers(device) {
return vertices.map(createVertexBuffer(device));
}

export async function init() {
const adapter = await getAdapter();
const device = await getDevice(adapter);

const { canvas, ctx } = getCanvasAndContext(device);
const pipeline = await createPipeline(device);

const vertexBuffers = createVertexBuffers(device);

return {
device,
canvas,
ctx,
pipeline,
vertexBuffers,
};
}

function renderPasses(scene) {
const { device, ctx, pipeline, vertexBuffers } = scene;
const commandEncoder = device.createCommandEncoder();

for (let i = 0; i < vertexBuffers.length; i++) {
const vertexBuffer = vertexBuffers[i];

const renderPassDescriptor = {
colorAttachments: [
{
view: ctx.getCurrentTexture().createView(),
clearValue: { r: 0.7, g: 0.7, b: 0.7, a: 1.0 },
loadOp: i === 0 ? "clear" : "load",
storeOp: "store",
},
],
};

const passEncoder =
commandEncoder.beginRenderPass(renderPassDescriptor);

passEncoder.setPipeline(pipeline);
passEncoder.setVertexBuffer(0, vertexBuffer);

passEncoder.draw(3, 1, 0, 0);

passEncoder.end();
}

device.queue.submit([commandEncoder.finish()]);
}

export async function main() {
const scene = await init();

renderPasses(scene);
}

document.addEventListener("DOMContentLoaded", main);
1 change: 1 addition & 0 deletions examples/blocks/src/gpu/.block
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
license: apache-2.0
93 changes: 93 additions & 0 deletions examples/blocks/src/gpu/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<!--

Copyright (c) 2017, the Perspective Authors.

This file is part of the Perspective library, distributed under the terms of
the Apache License 2.0. The full license can be found in the LICENSE file.

-->

<!DOCTYPE html>
<html>

<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" />

<script type="module" src="/node_modules/@finos/perspective/dist/cdn/perspective.js"></script>
<script type="module" src="/node_modules/@finos/perspective-viewer/dist/cdn/perspective-viewer.js"></script>
<script type="module"
src="/node_modules/@finos/perspective-viewer-datagrid/dist/cdn/perspective-viewer-datagrid.js"></script>
<script type="module" src="/node_modules/@finos/perspective-viewer-d3fc/dist/cdn/perspective-viewer-d3fc.js"></script>

<link rel="stylesheet" crossorigin="anonymous" href="/node_modules/@finos/perspective-viewer/dist/css/themes.css" />

<link rel="preload" href="/node_modules/@finos/perspective/dist/cdn/perspective.cpp.wasm" as="fetch"
type="application/wasm" crossorigin="anonymous" />
<link rel="preload" href="/node_modules/@finos/perspective-viewer/dist/cdn/perspective_bg.wasm" as="fetch"
type="application/wasm" crossorigin="anonymous" />

<link rel="preload" href="/node_modules/@finos/perspective/dist/cdn/perspective.worker.js" as="fetch"
type="application/javascript" crossorigin="anonymous" />
</script>

<script type="module">
import "/node_modules/@finos/perspective-gpu/dist/cdn/perspective-gpu.js";
import { worker } from "/node_modules/@finos/perspective/dist/cdn/perspective.js";

async function loadVehicleCrashes() {
const WORKER = worker();
const REQ = fetch(
"./vehicleCollisions_tiny.csv"
);
const resp = await REQ;
const csv = await resp.text();
const el =
document.getElementsByTagName("perspective-viewer")[0];
const table = WORKER.table(csv);
el.load(table);

const layout = {
plugin: "Gradient Heatmap",
plugin_config: {},
settings: true,
theme: "Pro Dark",
title: null,
group_by: [],
split_by: [],
columns: [],
columns: ["LATITUDE", "LONGITUDE"],
filter: [
["LATITUDE", ">=", 40.0],
["LONGITUDE", "<=", -73.0],
],
sort: [],
expressions: [],
aggregates: {}
}


el.restore(layout);
el.toggleConfig();
}

await customElements.whenDefined('perspective-viewer');

loadVehicleCrashes();
</script>

<style>
perspective-viewer {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
</style>
</head>

<body>
<perspective-viewer> </perspective-viewer>
</body>

</html>
Loading