diff --git a/docs/developer-guide/webgpu.md b/docs/developer-guide/webgpu.md index 8e242f51612..f214fb78b77 100644 --- a/docs/developer-guide/webgpu.md +++ b/docs/developer-guide/webgpu.md @@ -36,7 +36,7 @@ The table below covers the public layer exports from the layer packages. It is d | Module | Layer | WebGL | WebGPU | | --- | --- | --- | --- | | `@deck.gl/layers` | `ArcLayer` | ✅ | ✅ | -| `@deck.gl/layers` | `BitmapLayer` | ✅ | ❌ | +| `@deck.gl/layers` | `BitmapLayer` | ✅ | 🚧 | | `@deck.gl/layers` | `IconLayer` | ✅ | ✅ | | `@deck.gl/layers` | `LineLayer` | ✅ | ✅ | | `@deck.gl/layers` | `PointCloudLayer` | ✅ | ✅ | @@ -59,7 +59,7 @@ The table below covers the public layer exports from the layer packages. It is d | `@deck.gl/geo-layers` | `GreatCircleLayer` | ✅ | ❌ | | `@deck.gl/geo-layers` | `S2Layer` | ✅ | ❌ | | `@deck.gl/geo-layers` | `QuadkeyLayer` | ✅ | ❌ | -| `@deck.gl/geo-layers` | `TileLayer` | ✅ | ❌ | +| `@deck.gl/geo-layers` | `TileLayer` | ✅ | ✅ | | `@deck.gl/geo-layers` | `TripsLayer` | ✅ | ❌ | | `@deck.gl/geo-layers` | `H3ClusterLayer` | ✅ | ❌ | | `@deck.gl/geo-layers` | `H3HexagonLayer` | ✅ | ❌ | diff --git a/examples/website/map-tile/app.tsx b/examples/website/map-tile/app.tsx index eeb54469c25..593803afd84 100644 --- a/examples/website/map-tile/app.tsx +++ b/examples/website/map-tile/app.tsx @@ -13,6 +13,7 @@ import ZoomRangeWidget from './zoom-range-widget'; import type {Position, MapViewState} from '@deck.gl/core'; import type {TileLayerPickingInfo} from '@deck.gl/geo-layers'; +import type {Device} from '@luma.gl/core'; const INITIAL_VIEW_STATE: MapViewState = { latitude: 47.65, @@ -58,7 +59,8 @@ export default function App({ visibleMinZoom, visibleMaxZoom = 7, zoomOffset = 0, - useExtent = false + useExtent = false, + device }: { showBorder?: boolean; onTilesLoad?: () => void; @@ -69,6 +71,7 @@ export default function App({ visibleMaxZoom?: number; zoomOffset?: number; useExtent?: boolean; + device?: Device; }) { const [zoom, setZoom] = useState(INITIAL_VIEW_STATE.zoom); const onViewStateChange = useCallback( @@ -130,6 +133,7 @@ export default function App({ return ( void; onMetadataLoadError?: (error: Error) => void; + device?: Device; }) { const [selection, setSelection] = useState<{ x: number; @@ -79,7 +82,12 @@ export default function App({ return ( <> - + {selection && (
, + coordinateConversion: f32, + desaturate: f32, + tintColor: vec3, + transparentColor: vec4, +}; + +@group(0) @binding(auto) var bitmap: BitmapUniforms; +@group(0) @binding(auto) var bitmapTexture: texture_2d; +@group(0) @binding(auto) var bitmapTextureSampler: sampler; +`; + +const uniformBlockGLSL = `\ layout(std140) uniform bitmapUniforms { vec4 bounds; float coordinateConversion; @@ -26,8 +40,9 @@ export type BitmapProps = { export const bitmapUniforms = { name: 'bitmap', - vs: uniformBlock, - fs: uniformBlock, + source: uniformBlockWGSL, + vs: uniformBlockGLSL, + fs: uniformBlockGLSL, uniformTypes: { bounds: 'vec4', coordinateConversion: 'f32', diff --git a/modules/layers/src/bitmap-layer/bitmap-layer.ts b/modules/layers/src/bitmap-layer/bitmap-layer.ts index 3f5c7be75a6..6213dd0a914 100644 --- a/modules/layers/src/bitmap-layer/bitmap-layer.ts +++ b/modules/layers/src/bitmap-layer/bitmap-layer.ts @@ -4,6 +4,7 @@ import { Layer, + color as colorModule, project32, picking, CoordinateSystem, @@ -23,6 +24,7 @@ import {lngLatToWorld} from '@math.gl/web-mercator'; import createMesh from './create-mesh'; import {bitmapUniforms, BitmapProps} from './bitmap-layer-uniforms'; +import source from './bitmap-layer.wgsl'; import vs from './bitmap-layer-vertex'; import fs from './bitmap-layer-fragment'; @@ -129,7 +131,16 @@ export default class BitmapLayer extends Layer< }; getShaders() { - return super.getShaders({vs, fs, modules: [project32, picking, bitmapUniforms]}); + const isWebGPU = this.context.device.type === 'webgpu'; + + return super.getShaders({ + ...(isWebGPU && {source}), + vs, + fs, + modules: isWebGPU + ? [colorModule, project32, picking, bitmapUniforms] + : [project32, picking, bitmapUniforms] + }); } initializeState() { @@ -256,10 +267,18 @@ export default class BitmapLayer extends Layer< | | 0,1 --- 1,1 */ + const bufferLayout = + this.context.device.type === 'webgpu' + ? this.getAttributeManager()! + .getBufferLayouts({isInstanced: false}) + // WebGPU index buffers are bound separately from vertex buffer layouts. + .filter(layout => layout.name !== 'indices') + : this.getAttributeManager()!.getBufferLayouts(); + return new Model(this.context.device, { ...this.getShaders(), id: this.props.id, - bufferLayout: this.getAttributeManager()!.getBufferLayouts(), + bufferLayout, topology: 'triangle-list', isInstanced: false }); diff --git a/modules/layers/src/bitmap-layer/bitmap-layer.wgsl.ts b/modules/layers/src/bitmap-layer/bitmap-layer.wgsl.ts new file mode 100644 index 00000000000..308ee95881f --- /dev/null +++ b/modules/layers/src/bitmap-layer/bitmap-layer.wgsl.ts @@ -0,0 +1,145 @@ +// deck.gl +// SPDX-License-Identifier: MIT +// Copyright (c) vis.gl contributors + +export default /* wgsl */ `\ +struct Attributes { + @location(0) positions: vec3, + @location(1) positions64Low: vec3, + @location(2) texCoords: vec2, +}; + +struct Varyings { + @builtin(position) position: vec4, + @location(0) vTexCoord: vec2, + @location(1) vTexPos: vec2, + @location(2) pickingColor: vec3, + @location(3) pickingDepth: f32, +}; + +// from degrees to Web Mercator +fn lnglat_to_mercator(lnglat: vec2) -> vec2 { + let x = lnglat.x; + let y = clamp(lnglat.y, -89.9, 89.9); + return vec2( + radians(x) + PI, + PI + log(tan(PI * 0.25 + radians(y) * 0.5)) + ) * WORLD_SCALE; +} + +// from Web Mercator to degrees +fn mercator_to_lnglat(xy: vec2) -> vec2 { + let position = xy / WORLD_SCALE; + return degrees(vec2( + position.x - PI, + atan(exp(position.y - PI)) * 2.0 - PI * 0.5 + )); +} + +fn color_desaturate(colorValue: vec3) -> vec3 { + let luminance = (colorValue.r + colorValue.g + colorValue.b) * 0.333333333; + return mix(colorValue, vec3(luminance), bitmap.desaturate); +} + +fn color_tint(colorValue: vec3) -> vec3 { + return colorValue * bitmap.tintColor; +} + +fn apply_opacity(colorValue: vec3, alpha: f32) -> vec4 { + if (bitmap.transparentColor.a == 0.0) { + return vec4(colorValue, alpha); + } + let blendedAlpha = alpha + bitmap.transparentColor.a * (1.0 - alpha); + let highLightRatio = alpha / blendedAlpha; + let blendedRGB = mix(bitmap.transparentColor.rgb, colorValue, highLightRatio); + return vec4(blendedRGB, blendedAlpha); +} + +fn getUV(position: vec2) -> vec2 { + return vec2( + (position.x - bitmap.bounds[0]) / (bitmap.bounds[2] - bitmap.bounds[0]), + (position.y - bitmap.bounds[3]) / (bitmap.bounds[1] - bitmap.bounds[3]) + ); +} + +// Pack the top 12 bits of two normalized floats into three 8-bit values. +fn packUVsIntoRGB(uv: vec2) -> vec3 { + let uv8bit = floor(uv * 256.0); + let uvFraction = fract(uv * 256.0); + let uvFraction4bit = floor(uvFraction * 16.0); + let fractions = uvFraction4bit.x + uvFraction4bit.y * 16.0; + return vec3(uv8bit, fractions) / 255.0; +} + +@vertex +fn vertexMain(attributes: Attributes) -> Varyings { + var output: Varyings; + geometry.worldPosition = attributes.positions; + geometry.uv = attributes.texCoords; + geometry.pickingColor = picking_getPickingColorFromIndex(0u); + + let projectedPosition = project_position_to_clipspace_and_commonspace( + attributes.positions, + attributes.positions64Low, + vec3(0.0) + ); + geometry.position = projectedPosition.commonPosition; + output.position = projectedPosition.clipPosition; + output.vTexCoord = attributes.texCoords; + output.vTexPos = vec2(0.0); + output.pickingColor = geometry.pickingColor; + output.pickingDepth = output.position.z / output.position.w; + + if (bitmap.coordinateConversion < -0.5) { + output.vTexPos = geometry.position.xy + project.commonOrigin.xy; + } else if (bitmap.coordinateConversion > 0.5) { + output.vTexPos = geometry.worldPosition.xy; + } + + return output; +} + +@fragment +fn fragmentMain(input: Varyings) -> @location(0) vec4 { + var uv = input.vTexCoord; + if (bitmap.coordinateConversion < -0.5) { + uv = getUV(mercator_to_lnglat(input.vTexPos)); + } else if (bitmap.coordinateConversion > 0.5) { + uv = getUV(lnglat_to_mercator(input.vTexPos)); + } + + let bitmapColor = textureSample(bitmapTexture, bitmapTextureSampler, uv); + var fragColor = apply_opacity( + color_tint(color_desaturate(bitmapColor.rgb)), + bitmapColor.a * layer.opacity + ); + + geometry.uv = uv; + + if (picking.isActive > 0.5) { + if (picking.isAttribute > 0.5) { + return vec4(input.pickingDepth, 0.0, 0.0, 1.0); + } + return vec4(packUVsIntoRGB(uv), 1.0); + } + + if (picking.isHighlightActive > 0.5) { + let highlightedObjectColor = picking_normalizeColor(picking.highlightedObjectColor); + if (picking_isColorZero(abs(input.pickingColor - highlightedObjectColor))) { + let highLightAlpha = picking.highlightColor.a; + let blendedAlpha = highLightAlpha + fragColor.a * (1.0 - highLightAlpha); + if (blendedAlpha > 0.0) { + let highLightRatio = highLightAlpha / blendedAlpha; + fragColor = vec4( + mix(fragColor.rgb, picking.highlightColor.rgb, highLightRatio), + blendedAlpha + ); + } else { + fragColor = vec4(fragColor.rgb, 0.0); + } + } + } + + return deckgl_premultiplied_alpha(fragColor); +} +`; diff --git a/website/src/examples/tile-layer.js b/website/src/examples/tile-layer.js index ade7aeb1bc2..4c792306d93 100644 --- a/website/src/examples/tile-layer.js +++ b/website/src/examples/tile-layer.js @@ -12,6 +12,8 @@ import {makeExample} from '../components'; class MapTileDemo extends Component { static title = 'Raster Map Tiles'; + static hasDeviceTabs = true; + static code = `${GITHUB_TREE}/examples/website/map-tile`; static parameters = { @@ -61,6 +63,8 @@ class MapTileDemo extends Component { return (