Skip to content

Commit 777916a

Browse files
authored
feat(webgpu): Enable transparency for WebGPU and ported example layers (#9730)
1 parent 7dd6bad commit 777916a

File tree

13 files changed

+121
-80
lines changed

13 files changed

+121
-80
lines changed

examples/website/point-cloud/app.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,6 @@ export default function App({
6767
f64Pos[i] = pos.value[i];
6868
}
6969
(data as LASMesh).attributes.POSITION.value = f64Pos;
70-
// TODO: (kaapp) lack of transparency support for webgpu requires us to recolour the points
71-
// for now. We can remove this once we can create transparent webgpu canvas
72-
const color = (data as LASMesh).attributes.COLOR_0;
73-
for (let i = 0; i < color.value.length / 4; i++) {
74-
const index = i * 4;
75-
color.value[index] = 0xff; // r
76-
color.value[index + 1] = 0x00; // g
77-
color.value[index + 2] = 0x00; // b
78-
color.value[index + 3] = 0xff; // a
79-
}
8070
if (header.boundingBox) {
8171
const [mins, maxs] = header.boundingBox;
8272
// File contains bounding box info

modules/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export {default as FirstPersonViewport} from './viewports/first-person-viewport'
4646

4747
// Shader modules
4848
export {
49+
color,
4950
picking,
5051
project,
5152
project32,

modules/core/src/lib/deck.ts

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {webgl2Adapter} from '@luma.gl/webgl';
2121
import {Timeline} from '@luma.gl/engine';
2222
import {AnimationLoop} from '@luma.gl/engine';
2323
import {GL} from '@luma.gl/constants';
24-
import type {Device, DeviceProps, Framebuffer, Parameters} from '@luma.gl/core';
24+
import type {CanvasContextProps, Device, DeviceProps, Framebuffer, Parameters} from '@luma.gl/core';
2525
import type {ShaderModule} from '@luma.gl/shadertools';
2626

2727
import {Stats} from '@probe.gl/stats';
@@ -374,34 +374,7 @@ export default class Deck<ViewsT extends ViewOrViews = null> {
374374

375375
// Create a new device
376376
if (!deviceOrPromise) {
377-
const canvasContextUserProps = this.props.deviceProps?.createCanvasContext;
378-
const canvasContextProps =
379-
typeof canvasContextUserProps === 'object' ? canvasContextUserProps : undefined;
380-
381-
// In deck.gl v9, Deck always bundles and adds a webgl2Adapter.
382-
// This behavior is expected to change in deck.gl v10 to support WebGPU only builds.
383-
const deviceProps = {adapters: [], ...props.deviceProps};
384-
if (!deviceProps.adapters.includes(webgl2Adapter)) {
385-
deviceProps.adapters.push(webgl2Adapter);
386-
}
387-
388-
// Create the "best" device supported from the registered adapters
389-
deviceOrPromise = luma.createDevice({
390-
// luma by default throws if a device is already attached
391-
// asynchronous device creation could happen after finalize() is called
392-
// TODO - createDevice should support AbortController?
393-
_reuseDevices: true,
394-
// tests can't handle WebGPU devices yet so we force WebGL2 unless overridden
395-
type: 'webgl',
396-
...deviceProps,
397-
// In deck.gl v10 we may emphasize multi canvas support and unwind this prop wrapping
398-
createCanvasContext: {
399-
...canvasContextProps,
400-
canvas: this._createCanvas(props),
401-
useDevicePixels: this.props.useDevicePixels,
402-
autoResize: true
403-
}
404-
});
377+
deviceOrPromise = this._createDevice(props);
405378
}
406379

407380
this.animationLoop = this._createAnimationLoop(deviceOrPromise, props);
@@ -874,6 +847,44 @@ export default class Deck<ViewsT extends ViewOrViews = null> {
874847
});
875848
}
876849

850+
// Create a device from the deviceProps, assigning required defaults
851+
private _createDevice(props: DeckProps<ViewsT>): Promise<Device> {
852+
const canvasContextUserProps = this.props.deviceProps?.createCanvasContext;
853+
const canvasContextProps =
854+
typeof canvasContextUserProps === 'object' ? canvasContextUserProps : undefined;
855+
856+
// In deck.gl v9, Deck always bundles and adds a webgl2Adapter.
857+
// This behavior is expected to change in deck.gl v10 to support WebGPU only builds.
858+
const deviceProps = {adapters: [], ...props.deviceProps};
859+
if (!deviceProps.adapters.includes(webgl2Adapter)) {
860+
deviceProps.adapters.push(webgl2Adapter);
861+
}
862+
863+
const defaultCanvasProps: CanvasContextProps = {
864+
// we must use 'premultiplied' canvas for webgpu to enable transparency and match shaders
865+
alphaMode: this.props.deviceProps?.type === 'webgpu' ? 'premultiplied' : undefined
866+
};
867+
868+
// Create the "best" device supported from the registered adapters
869+
return luma.createDevice({
870+
// luma by default throws if a device is already attached
871+
// asynchronous device creation could happen after finalize() is called
872+
// TODO - createDevice should support AbortController?
873+
_reuseDevices: true,
874+
// tests can't handle WebGPU devices yet so we force WebGL2 unless overridden
875+
type: 'webgl',
876+
...deviceProps,
877+
// In deck.gl v10 we may emphasize multi canvas support and unwind this prop wrapping
878+
createCanvasContext: {
879+
...defaultCanvasProps,
880+
...canvasContextProps,
881+
canvas: this._createCanvas(props),
882+
useDevicePixels: this.props.useDevicePixels,
883+
autoResize: true
884+
}
885+
});
886+
}
887+
877888
// Get the most relevant view state: props.viewState, if supplied, shadows internal viewState
878889
// TODO: For backwards compatibility ensure numeric width and height is added to the viewState
879890
private _getViewState(): ViewStateObject<ViewsT> | null {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// deck.gl
2+
// SPDX-License-Identifier: MIT
3+
// Copyright (c) vis.gl contributors
4+
5+
import {ShaderModule} from '@luma.gl/shadertools';
6+
import {LayerProps} from '../../types/layer-props';
7+
8+
const colorWGSL = /* WGSL */ `
9+
10+
struct ColorUniforms {
11+
opacity: f32,
12+
};
13+
14+
var<private> color: ColorUniforms = ColorUniforms(1.0);
15+
// TODO (kaapp) avoiding binding index collisions to handle layer opacity
16+
// requires some thought.
17+
// @group(0) @binding(0) var<uniform> color: ColorUniforms;
18+
19+
@must_use
20+
fn deckgl_premultiplied_alpha(fragColor: vec4<f32>) -> vec4<f32> {
21+
return vec4(fragColor.rgb * fragColor.a, fragColor.a);
22+
};
23+
`;
24+
25+
export type ColorProps = {
26+
/**
27+
* Opacity of the layer, between 0 and 1. Default 1.
28+
*/
29+
opacity?: number;
30+
};
31+
32+
export type ColorUniforms = {
33+
opacity?: number;
34+
};
35+
36+
export default {
37+
name: 'color',
38+
dependencies: [],
39+
source: colorWGSL,
40+
getUniforms: (_props: Partial<ColorProps>) => {
41+
// TODO (kaapp) Handle layer opacity
42+
// apply gamma to opacity to make it visually "linear"
43+
// TODO - v10: use raw opacity?
44+
// opacity: Math.pow(props.opacity!, 1 / 2.2)
45+
return {};
46+
},
47+
uniformTypes: {
48+
opacity: 'f32'
49+
}
50+
// @ts-ignore TODO v9.1
51+
} as const satisfies ShaderModule<LayerProps, ColorUniforms, {}>;

modules/core/src/shaderlib/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {ShaderAssembler} from '@luma.gl/shadertools';
66

77
import {gouraudMaterial, phongMaterial} from '@luma.gl/shadertools';
88
import {layerUniforms} from './misc/layer-uniforms';
9+
import color from './color/color';
910
import geometry from './misc/geometry';
1011
import project from './project/project';
1112
import project32 from './project32/project32';
@@ -47,7 +48,7 @@ export function getShaderAssembler(language: 'glsl' | 'wgsl'): ShaderAssembler {
4748
return shaderAssembler;
4849
}
4950

50-
export {layerUniforms, picking, project, project32, gouraudMaterial, phongMaterial, shadow};
51+
export {layerUniforms, color, picking, project, project32, gouraudMaterial, phongMaterial, shadow};
5152

5253
// Useful for custom shader modules
5354
export type {ProjectProps, ProjectUniforms} from './project/viewport-uniforms';

modules/layers/src/line-layer/line-layer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import {
66
Layer,
77
project32,
8+
color,
89
picking,
910
UNIT,
1011
LayerProps,
@@ -114,7 +115,7 @@ export default class LineLayer<DataT = any, ExtraProps extends {} = {}> extends
114115
}
115116

116117
getShaders() {
117-
return super.getShaders({vs, fs, source, modules: [project32, picking, lineUniforms]});
118+
return super.getShaders({vs, fs, source, modules: [project32, color, picking, lineUniforms]});
118119
}
119120

120121
// This layer has its own wrapLongitude logic

modules/layers/src/line-layer/line-layer.wgsl.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,6 @@
33
// Copyright (c) vis.gl contributors
44

55
export const shaderWGSL = /* wgsl */ `\
6-
// TODO(ibgreen): Hack for Layer uniforms (move to new "color" module?)
7-
struct LayerUniforms {
8-
opacity: f32,
9-
};
10-
var<private> layer: LayerUniforms = LayerUniforms(1.0);
11-
// @group(0) @binding(1) var<uniform> layer: LayerUniforms;
12-
136
// ---------- Helper Structures & Functions ----------
147
158
// Placeholder filter functions.
@@ -43,17 +36,17 @@ fn splitLine(a: vec3<f32>, b: vec3<f32>, x: f32) -> vec3<f32> {
4336
4437
// ---------- Uniforms & Global Structures ----------
4538
46-
// Uniforms for line, layer, and project are assumed to be defined elsewhere.
39+
// Uniforms for line, color, and project are assumed to be defined elsewhere.
4740
// For example:
4841
//
4942
// @group(0) @binding(0)
5043
// var<uniform> line: LineUniform;
5144
//
52-
// struct LayerUniform {
45+
// struct ColorUniform {
5346
// opacity: f32,
5447
// };
5548
// @group(0) @binding(1)
56-
// var<uniform> layer: LayerUniform;
49+
// var<uniform> color: ColorUniform;
5750
//
5851
// struct ProjectUniform {
5952
// viewportSize: vec2<f32>,
@@ -150,7 +143,7 @@ fn vertexMain(
150143
let finalPosition: vec4<f32> = filteredP + vec4<f32>(clipOffset, 0.0, 0.0);
151144
152145
// Compute color.
153-
var vColor: vec4<f32> = vec4<f32>(instanceColors.rgb, instanceColors.a * layer.opacity);
146+
var vColor: vec4<f32> = vec4<f32>(instanceColors.rgb, instanceColors.a * color.opacity);
154147
// vColor = deckgl_filter_color(vColor, geometry);
155148
156149
var output: Varyings;
@@ -175,6 +168,9 @@ fn fragmentMain(
175168
// Apply the deck.gl filter to the color.
176169
fragColor = deckgl_filter_color(fragColor, geometry);
177170
171+
// Apply premultiplied alpha as required by transparent canvas
172+
fragColor = deckgl_premultiplied_alpha(fragColor);
173+
178174
return fragColor;
179175
}
180176
`;

modules/layers/src/point-cloud-layer/point-cloud-layer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import {
66
Layer,
7+
color,
78
project32,
89
picking,
910
UNIT,
@@ -131,7 +132,7 @@ export default class PointCloudLayer<DataT = any, ExtraPropsT extends {} = {}> e
131132
vs,
132133
fs,
133134
source,
134-
modules: [project32, gouraudMaterial, picking, pointCloudUniforms]
135+
modules: [project32, color, gouraudMaterial, picking, pointCloudUniforms]
135136
});
136137
}
137138

modules/layers/src/point-cloud-layer/point-cloud-layer.wgsl.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,6 @@
33
// Copyright (c) vis.gl contributors
44

55
export default /* wgsl */ `\
6-
// TODO(ibgreen): Hack for Layer uniforms (move to new "color" module?)
7-
8-
struct LayerUniforms {
9-
opacity: f32,
10-
};
11-
12-
var<private> layer: LayerUniforms = LayerUniforms(1.0);
13-
// @group(0) @binding(1) var<uniform> layer: LayerUniforms;
14-
15-
// Main shaders
166
struct ConstantAttributes {
177
instanceNormals: vec3<f32>,
188
instanceColors: vec4<f32>,
@@ -73,7 +63,7 @@ fn vertexMain(attributes: Attributes) -> Varyings {
7363
let lightColor = lighting_getLightColor2(attributes.instanceColors.rgb, project.cameraPosition, geometry.position.xyz, geometry.normal);
7464
7565
// Apply opacity to instance color, or return instance picking color
76-
varyings.vColor = vec4(lightColor, attributes.instanceColors.a * layer.opacity);
66+
varyings.vColor = vec4(lightColor, attributes.instanceColors.a * color.opacity);
7767
// DECKGL_FILTER_COLOR(vColor, geometry);
7868
7969
return varyings;
@@ -94,6 +84,9 @@ fn fragmentMain(varyings: Varyings) -> @location(0) vec4<f32> {
9484
fragColor = varyings.vColor;
9585
// DECKGL_FILTER_COLOR(fragColor, geometry);
9686
87+
// Apply premultiplied alpha as required by transparent canvas
88+
fragColor = deckgl_premultiplied_alpha(fragColor);
89+
9790
return fragColor;
9891
}
9992
`;

modules/layers/src/scatterplot-layer/scatterplot-layer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: MIT
33
// Copyright (c) vis.gl contributors
44

5-
import {Layer, project32, picking, UNIT} from '@deck.gl/core';
5+
import {Layer, color, project32, picking, UNIT} from '@deck.gl/core';
66
import {Model, Geometry} from '@luma.gl/engine';
77

88
import {scatterplotUniforms, ScatterplotProps} from './scatterplot-layer-uniforms';
@@ -175,7 +175,7 @@ export default class ScatterplotLayer<DataT = any, ExtraPropsT extends {} = {}>
175175
vs,
176176
fs,
177177
source,
178-
modules: [project32, picking, scatterplotUniforms]
178+
modules: [project32, color, picking, scatterplotUniforms]
179179
});
180180
}
181181

0 commit comments

Comments
 (0)