Skip to content

Commit

Permalink
Performance and structure changes
Browse files Browse the repository at this point in the history
  • Loading branch information
KaNaDaAT committed Jan 16, 2024
1 parent 7dccc41 commit abf54a1
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 229 deletions.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[Demo](https://kanadaat.github.io/vega-webgpu/test?version=1_0_0)

A specific Vega WebGPU Extension can be used by using the queryparameter `version=1_0_0` otherwise the latest will be used.
A specific Vega WebGPU Extension can be used by using the queryparameter (as for example `version=1_0_0`) otherwise the latest will be used.

The Vega WebGPU Extension is created by [KaNaDaAT](https://github.com/KaNaDaAT) based on the already existing efforts of [lsh](https://github.com/lsh).

Expand Down Expand Up @@ -46,6 +46,8 @@ The WebGPU renderer was developed for Vega 5.19.1. Other Versions may work as we
</body>
```

The example shows how it is possible to always use the "latest" version.

For more infos look at [Hosted Versions](#hosted-versions).

## Building Locally
Expand Down Expand Up @@ -95,12 +97,17 @@ function render(spec) {
});
return view.runAsync();
}
view._renderer.debugLog = true;
view._renderer.wgOptions.debugLog = true;
// For Version 1.0.0 it is:
// view._renderer.wgOptions.debugLog = true;
```
| Option | Description | Default | Version | |
|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|---------|---|
| debugLog | Allows the renderer to log the time needed for the frame | false | 1.0.0 | |
| simpleLine | When set to `false` the renderer will use a different type of line rendering that is optimized for small amount of lines with alot of points (curved lines) | true | 1.0.0 | |
| cacheShape | Allows shapes to cache its entries so it might be faster (experimental) | false | 1.1.0 | |

There are different options available for the WebGPU renderer.
- **debugLog**: Allows the renderer to log the time needed for the frame
- **simpleLine**: Allows the renderer to use a different type of line rendering that is optimized for small amount of lines with alot of points (curved lines)
**Note:** Its a bit different on Version 1.0.0. Have a look at the demos index.js

## Contributing

Expand Down
69 changes: 37 additions & 32 deletions src/WebGPURenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import marks from './marks/index';
import { inherits } from 'vega-util';
import { drawCanvas } from './util/image';
import { Renderer as RendererFunctions } from './util/renderer';
import { GPUScene } from './types/gpuscene.js';
import { GPUVegaCanvasContext, GPUVegaOptions, GPUVegaScene } from './types/gpuVegaTypes.js';


import symbolShader from './shaders/symbol.wgsl';
import lineShader from './shaders/line.wgsl';
import ruleShader from './shaders/rule.wgsl';
import slineShader from './shaders/sline.wgsl';
import triangleShader from './shaders/triangles.wgsl';
import rectShader from './shaders/rect.wgsl';
Expand All @@ -32,9 +33,9 @@ const viewBounds = (origin: [number, number], width: number, height: number) =>
new Bounds().set(0, 0, width, height).translate(-origin[0], -origin[1]);

inherits(WebGPURenderer, Renderer, {
initialize(el: HTMLCanvasElement, width: number, height: number, origin: [number, number]) {
initialize(el: HTMLCanvasElement, width: number, height: number, origin: [x: number, y: number]) {
this._canvas = document.createElement('canvas'); // instantiate a small canvas
this._ctx = this._canvas.getContext('webgpu');
const ctx: GPUVegaCanvasContext = this._canvas.getContext('webgpu');
this._textCanvas = document.createElement('canvas');
this._textContext = this._textCanvas.getContext('2d');
if (el) {
Expand All @@ -51,24 +52,28 @@ inherits(WebGPURenderer, Renderer, {
el.appendChild(this._textCanvas);
}
this._canvas._textCanvas = this._textCanvas
this._ctx._textContext = this._textContext;
this._ctx._renderer = this;
ctx._textContext = this._textContext;
ctx._renderer = this;
this._bgcolor = "#ffffff";

this._uniforms = {
resolution: [width, height],
origin: origin,
dpi: window.devicePixelRatio || 1,
};
this._ctx._uniforms = this._uniforms;
ctx._uniforms = this._uniforms;

this._ctx._pathCache = {};
this._ctx._pathCacheSize = 0;
this._ctx._geometryCache = {};
this._ctx._geometryCacheSize = 0;
ctx._pathCache = {};
ctx._pathCacheSize = 0;
ctx._geometryCache = {};
ctx._geometryCacheSize = 0;
this._ctx = ctx;

this.simpleLine = true;
this.debugLog = false;
const wgOptions = {} as GPUVegaOptions;
wgOptions.simpleLine = true;
wgOptions.debugLog = false;
wgOptions.cacheShapes = false;
this.wgOptions = wgOptions;

this._renderCount = 0;

Expand Down Expand Up @@ -104,7 +109,7 @@ inherits(WebGPURenderer, Renderer, {
return this._textCanvas;
},

context(): GPUCanvasContext {
context(): GPUVegaCanvasContext {
return this._ctx ? this._ctx : null;
},

Expand All @@ -130,31 +135,31 @@ inherits(WebGPURenderer, Renderer, {

async _reinit() {
let device = this.device();
let ctx = this.context();
let ctx: GPUVegaCanvasContext = this.context();
if (!device || !ctx) {
const adapter = await navigator.gpu.requestAdapter();
const adapter = await navigator.gpu.requestAdapter({ powerPreference: "high-performance" });
device = await adapter.requestDevice();
this._adapter = adapter;
this._device = device;
const presentationFormat = navigator.gpu.getPreferredCanvasFormat() as GPUTextureFormat;
RendererFunctions.colorFormat = presentationFormat;
this._prefferedFormat = presentationFormat;
RendererFunctions.colorFormat = presentationFormat;
ctx = this._canvas.getContext('webgpu');
this._ctx.configure({
ctx.configure({
device,
format: presentationFormat,
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
alphaMode: 'premultiplied',
});
this._ctx = ctx;
this.cacheShaders();
this._renderPassDescriptor = RendererFunctions.createRenderPassDescriptor("Bundler", this.clearColor(), this.depthTexture().createView())
}
return { device, ctx };
},

_render(scene: GPUScene) {
_render(scene: GPUVegaScene) {
(async () => {
let { device, ctx } = (await this._reinit()) as { device: GPUDevice, ctx: any};
let { device, ctx } = (await this._reinit()) as { device: GPUDevice, ctx: GPUVegaCanvasContext };
RendererFunctions.startFrame();
let o = this._origin,
w = this._width,
Expand All @@ -170,14 +175,13 @@ inherits(WebGPURenderer, Renderer, {
this.draw(device, ctx, scene, vb);
const t2 = performance.now();
device.queue.onSubmittedWorkDone().then(() => {
if (this.debugLog == true) {
if (this.wgOptions.debugLog === true) {
const t3 = performance.now();
console.log(`Render Time (${this._renderCount++}): ${((t3 - t1) / 1).toFixed(3)}ms (Draw: ${((t2 - t1) / 1).toFixed(3)}ms, WebGPU: ${((t3 - t2) / 1).toFixed(3)}ms)`);
}
});
const renderPassDescriptor = RendererFunctions.createRenderPassDescriptor("Bundler", this.clearColor(), this.depthTexture().createView())
renderPassDescriptor.colorAttachments[0].view = ctx.getCurrentTexture().createView();
RendererFunctions.submitBundles(device, renderPassDescriptor);
this._renderPassDescriptor.colorAttachments[0].view = ctx.getCurrentTexture().createView();
await RendererFunctions.submitQueue(device);
})();

return this;
Expand All @@ -190,20 +194,22 @@ inherits(WebGPURenderer, Renderer, {
return this;
},

draw(device: GPUDevice, ctx: GPUCanvasContext, scene: GPUScene & { marktype: string }, transform: Bounds) {
draw(device: GPUDevice, ctx: GPUVegaCanvasContext, scene: GPUVegaScene & { marktype: string }, transform: Bounds) {
const mark = marks[scene.marktype];
if (mark == null) {
console.error(`Unknown mark type: '${scene.marktype}'`)
} else {
// ToDo: Set Options
scene._format = this.prefferedFormat();
ctx.depthTexture = this.depthTexture();
ctx.background = this.clearColor();
mark.draw.call(this, device, ctx, scene, transform);
}
},

clear() {
const device = this.device() as GPUDevice;
const context = this.context() as GPUCanvasContext;
const context = this.context() as GPUVegaCanvasContext;

const textureView = context.getCurrentTexture().createView();
const renderPassDescriptor = {
label: 'Background',
Expand Down Expand Up @@ -243,27 +249,26 @@ inherits(WebGPURenderer, Renderer, {
usage: GPUTextureUsage.RENDER_ATTACHMENT,
} as GPUTextureDescriptor);
this._depthTexture.device = this._device;
this._renderPassDescriptor = RendererFunctions.createRenderPassDescriptor("Bundler", this.clearColor(), this.depthTexture().createView())
return this._depthTexture;
},

clearColor(): GPUColor {
return (this._bgcolor ? Color.from(this._bgcolor) : { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }) as GPUColor;
},

prefferedFormat(): GPUTextureFormat {
return this._prefferedFormat != null ? this._prefferedFormat : null;
},


cacheShaders() {
const device: GPUDevice = this.device();
const context = this.context();
const context: GPUVegaCanvasContext = this.context();
context._shaderCache = {};
context._shaderCache["Symbol"] = device.createShaderModule({ code: symbolShader, label: 'Symbol Shader' });
context._shaderCache["Line"] = device.createShaderModule({ code: lineShader, label: 'Line Shader' });
context._shaderCache["Rule"] = device.createShaderModule({ code: ruleShader, label: 'Rule Shader' });
context._shaderCache["SLine"] = device.createShaderModule({ code: slineShader, label: 'SLine Shader' });
context._shaderCache["Path"] = device.createShaderModule({ code: triangleShader, label: 'Triangle Shader' });
context._shaderCache["Rect"] = device.createShaderModule({ code: rectShader, label: 'Rect Shader' });
context._shaderCache["Group"] = device.createShaderModule({ code: rectShader, label: 'Group Shader' });
context._shaderCache["Arc"] = device.createShaderModule({ code: arcShader, label: 'Arc Shader' });
context._shaderCache["Shape"] = device.createShaderModule({ code: shapeShader, label: 'Shape Shader' });
context._shaderCache["Area"] = device.createShaderModule({ code: areaShader, label: 'Area Shader' });
Expand Down
30 changes: 16 additions & 14 deletions src/marks/arc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Bounds } from 'vega-scenegraph';
import { Color } from '../util/color.js';
import { SceneItem, SceneGroup } from 'vega-typings';
import { GPUScene } from '../types/gpuscene.js';
import { GPUVegaScene, GPUVegaCanvasContext } from '../types/gpuVegaTypes.js';
import { VertexBufferManager } from '../util/vertexManager.js';
import { BufferManager } from '../util/bufferManager.js';
import { Renderer } from '../util/renderer.js';
Expand Down Expand Up @@ -29,50 +29,52 @@ let _bufferManager: BufferManager = null;
let _shader: GPUShaderModule = null;
let _vertextBufferManager: VertexBufferManager = null;
let _pipeline: GPURenderPipeline = null;
let _renderPassDescriptor: GPURenderPassDescriptor = null;
let isInitialized: boolean = false;

function initialize(device: GPUDevice, ctx: GPUCanvasContext, scene: GPUScene, vb: Bounds) {
function initialize(device: GPUDevice, ctx: GPUVegaCanvasContext, vb: Bounds) {
if (_device != device) {
_device = device;
isInitialized = false;
}

if (!isInitialized) {
_bufferManager = new BufferManager(device, drawName, (ctx as any)._uniforms.resolution, [vb.x1, vb.y1]);
_shader = (ctx as any)._shaderCache["Arc"] as GPUShaderModule;
if (!isInitialized || true) {
_bufferManager = new BufferManager(device, drawName, ctx._uniforms.resolution, [vb.x1, vb.y1]);
_shader = ctx._shaderCache["Arc"] as GPUShaderModule;
_vertextBufferManager = new VertexBufferManager(
['float32x3', 'float32x4'], // position, color
['float32x2'] // center
);
_pipeline = Renderer.createRenderPipeline(drawName, device, _shader, scene._format, _vertextBufferManager.getBuffers());
_pipeline = Renderer.createRenderPipeline(drawName, device, _shader, Renderer.colorFormat, _vertextBufferManager.getBuffers());
_renderPassDescriptor = Renderer.createRenderPassDescriptor(drawName, ctx.background, ctx.depthTexture.createView());
isInitialized = true;
}
_renderPassDescriptor.colorAttachments[0].view = ctx.getCurrentTexture().createView();
}

function draw(device: GPUDevice, ctx: GPUCanvasContext, scene: GPUScene, vb: Bounds) {
function draw(device: GPUDevice, ctx: GPUVegaCanvasContext, scene: GPUVegaScene, vb: Bounds) {
const items = scene.items as SceneArc[];
if (!items?.length) {
return;
}

initialize(device, ctx, scene, vb);
_bufferManager.setResolution((ctx as any)._uniforms.resolution);
initialize(device, ctx, vb);
_bufferManager.setResolution(ctx._uniforms.resolution);
_bufferManager.setOffset([vb.x1, vb.y1]);
const uniformBuffer = _bufferManager.createUniformBuffer();
const uniformBindGroup = Renderer.createUniformBindGroup(drawName, device, _pipeline, uniformBuffer);

for (var itemStr in items) {
const item = items[itemStr];
const geometryData = createGeometryData(ctx, item);

for (let i = 0; i < geometryData.length; i++) {
const geometryCount = geometryData[i].length / _vertextBufferManager.getVertexLength();
if (geometryCount == 0)
continue;
const geometryBuffer = _bufferManager.createGeometryBuffer(geometryData[i]);
const instanceBuffer = _bufferManager.createInstanceBuffer(createPosition(item));

Renderer.bundle2(device, _pipeline, [geometryCount], [geometryBuffer, instanceBuffer], [uniformBindGroup]);
Renderer.queue2(device, _pipeline, _renderPassDescriptor, [geometryCount], [geometryBuffer, instanceBuffer], [uniformBindGroup]);
}
}
}
Expand All @@ -88,13 +90,13 @@ function createPosition(item: SceneItem): Float32Array {


function createGeometryData(
context: GPUCanvasContext,
context: GPUVegaCanvasContext,
item: SceneArc
): [geometryData: Float32Array, strokeGeometryData: Float32Array] {
// @ts-ignore
const shapeGeom = arc(context, item);
const geometry = geometryForItem(context, item, shapeGeom);

const geometryData = new Float32Array(geometry.fillCount * 7);
const strokeGeometryData = new Float32Array(geometry.strokeCount * 7);
const fill = Color.from(item.fill, item.opacity, item.fillOpacity);
Expand Down
26 changes: 15 additions & 11 deletions src/marks/area.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Bounds } from 'vega-scenegraph';
import { Color } from '../util/color.js';
import { SceneGroup, SceneItem } from 'vega-typings';
import { GPUScene } from '../types/gpuscene.js';
import { GPUVegaScene, GPUVegaCanvasContext } from '../types/gpuVegaTypes.js';
import { VertexBufferManager } from '../util/vertexManager.js';
import { BufferManager } from '../util/bufferManager.js';
import { Renderer } from '../util/renderer.js';
Expand All @@ -28,34 +28,37 @@ let _bufferManager: BufferManager = null;
let _shader: GPUShaderModule = null;
let _vertextBufferManager: VertexBufferManager = null;
let _pipeline: GPURenderPipeline = null;
let _renderPassDescriptor: GPURenderPassDescriptor = null;
let isInitialized: boolean = false;

function initialize(device: GPUDevice, ctx: GPUCanvasContext, scene: GPUScene, vb: Bounds) {
function initialize(device: GPUDevice, ctx: GPUVegaCanvasContext, vb: Bounds) {
if (_device != device) {
_device = device;
isInitialized = false;
}

if (!isInitialized) {
_bufferManager = new BufferManager(device, drawName, (ctx as any)._uniforms.resolution, [vb.x1, vb.y1]);
_shader = (ctx as any)._shaderCache["Area"] as GPUShaderModule;
_bufferManager = new BufferManager(device, drawName, ctx._uniforms.resolution, [vb.x1, vb.y1]);
_shader = ctx._shaderCache["Area"] as GPUShaderModule;
_vertextBufferManager = new VertexBufferManager(
['float32x3', 'float32x4'], // position, color
[] // center
);
_pipeline = Renderer.createRenderPipeline(drawName, device, _shader, scene._format, _vertextBufferManager.getBuffers());
_pipeline = Renderer.createRenderPipeline(drawName, device, _shader, Renderer.colorFormat, _vertextBufferManager.getBuffers());
_renderPassDescriptor = Renderer.createRenderPassDescriptor(drawName, ctx.background, ctx.depthTexture.createView());
isInitialized = true;
}
_renderPassDescriptor.colorAttachments[0].view = ctx.getCurrentTexture().createView();
}

function draw(device: GPUDevice, ctx: GPUCanvasContext, scene: GPUScene, vb: Bounds) {
function draw(device: GPUDevice, ctx: GPUVegaCanvasContext, scene: GPUVegaScene, vb: Bounds) {
const items = scene.items as SceneArea[];
if (!items?.length) {
return;
}

initialize(device, ctx, scene, vb);
_bufferManager.setResolution((ctx as any)._uniforms.resolution);
initialize(device, ctx, vb);
_bufferManager.setResolution(ctx._uniforms.resolution);
_bufferManager.setOffset([vb.x1, vb.y1]);

const item = items[0];
Expand All @@ -68,19 +71,20 @@ function draw(device: GPUDevice, ctx: GPUCanvasContext, scene: GPUScene, vb: Bou
if (geometryCount == 0)
continue;
const geometryBuffer = _bufferManager.createGeometryBuffer(geometryData[i]);
Renderer.bundle2(device, _pipeline, [geometryCount], [geometryBuffer], [uniformBindGroup]);
// Renderer.queue2(device, _pipeline, [geometryCount], [geometryBuffer], [uniformBindGroup]);
Renderer.queue2(device, _pipeline, _renderPassDescriptor, [geometryCount], [geometryBuffer], [uniformBindGroup]);
}
}

function createGeometryData(
context: GPUCanvasContext,
context: GPUVegaCanvasContext,
item: SceneArea,
items: SceneArea[]
): [geometryData: Float32Array, strokeGeometryData: Float32Array] {
// @ts-ignore
const shapeGeom = area(context, items);
const geometry = geometryForItem(context, item, shapeGeom);

const geometryData = new Float32Array(geometry.fillCount * 7);
const strokeGeometryData = new Float32Array(geometry.strokeCount * 7);
const fill = Color.from2(item.fill, item.opacity, item.fillOpacity);
Expand Down
Loading

0 comments on commit abf54a1

Please sign in to comment.