Skip to content
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
6 changes: 3 additions & 3 deletions modules/core/src/lib/attribute/data-column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,10 @@ export default class DataColumn<Options, State> {
options: Partial<ShaderAttributeOptions> | null = null
): BufferLayout {
const accessor = this.getAccessor();
const attributes: BufferAttributeLayout[] = [];
const attributes: (BufferAttributeLayout | null)[] = [];
const result: BufferLayout = {
name: this.id,
byteStride: getStride(accessor),
attributes
byteStride: getStride(accessor)
};

if (this.doublePrecision) {
Expand Down Expand Up @@ -307,6 +306,7 @@ export default class DataColumn<Options, State> {
} else {
attributes.push(getBufferAttributeLayout(attributeName, accessor, this.device.type));
}
result.attributes = attributes.filter(Boolean) as BufferAttributeLayout[];
return result;
}

Expand Down
6 changes: 5 additions & 1 deletion modules/core/src/lib/attribute/gl-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ export function getBufferAttributeLayout(
name: string,
accessor: BufferAccessor,
deviceType: 'webgpu' | 'wegbgl' | string
): BufferAttributeLayout {
): BufferAttributeLayout | null {
if ((accessor.size as number) > 4) {
// Definitely not valid. TODO - stricter validation?
return null;
}
// TODO(ibgreen): WebGPU change. Currently we always use normalized 8 bit integers
const type = deviceType === 'webgpu' && accessor.type === 'uint8' ? 'unorm8' : accessor.type;
return {
Expand Down
65 changes: 35 additions & 30 deletions modules/layers/src/icon-layer/icon-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import {Layer, project32, picking, log, UNIT} from '@deck.gl/core';
import {Layer, color, project32, picking, log, UNIT} from '@deck.gl/core';
import {SamplerProps, Texture} from '@luma.gl/core';
import {Model, Geometry} from '@luma.gl/engine';

import {iconUniforms, IconProps} from './icon-layer-uniforms';
import vs from './icon-layer-vertex.glsl';
import fs from './icon-layer-fragment.glsl';
import {shaderWGSL as source} from './icon-layer.wgsl';
import IconManager from './icon-manager';

import type {
Expand All @@ -25,6 +26,7 @@ import type {
} from '@deck.gl/core';

import type {UnpackedIcon, IconMapping, LoadIconErrorContext} from './icon-manager';
import {Parameters} from '@luma.gl/core';

type _IconLayerProps<DataT> = {
data: LayerDataSource<DataT>;
Expand Down Expand Up @@ -139,7 +141,7 @@ export default class IconLayer<DataT = any, ExtraPropsT extends {} = {}> extends
};

getShaders() {
return super.getShaders({vs, fs, modules: [project32, picking, iconUniforms]});
return super.getShaders({vs, fs, source, modules: [project32, color, picking, iconUniforms]});
}

initializeState() {
Expand All @@ -166,24 +168,25 @@ export default class IconLayer<DataT = any, ExtraPropsT extends {} = {}> extends
accessor: 'getSize',
defaultValue: 1
},
instanceOffsets: {
size: 2,
accessor: 'getIcon',
// eslint-disable-next-line @typescript-eslint/unbound-method
transform: this.getInstanceOffset
},
instanceIconFrames: {
size: 4,
accessor: 'getIcon',
// eslint-disable-next-line @typescript-eslint/unbound-method
transform: this.getInstanceIconFrame
},
instanceColorModes: {
size: 1,
type: 'uint8',
instanceIconDefs: {
size: 7,
accessor: 'getIcon',
// eslint-disable-next-line @typescript-eslint/unbound-method
transform: this.getInstanceColorMode
transform: this.getInstanceIconDef,
shaderAttributes: {
instanceOffsets: {
size: 2,
elementOffset: 0
},
instanceIconFrames: {
size: 4,
elementOffset: 2
},
instanceColorModes: {
size: 1,
elementOffset: 6
}
}
},
instanceColors: {
size: this.props.colorFormat.length,
Expand Down Expand Up @@ -286,6 +289,13 @@ export default class IconLayer<DataT = any, ExtraPropsT extends {} = {}> extends
}

protected _getModel(): Model {
const parameters =
this.context.device.type === 'webgpu'
? ({
depthWriteEnabled: true,
depthCompare: 'less-equal'
} satisfies Parameters)
: undefined;
// The icon-layer vertex shader uses 2d positions
// specifed via: in vec2 positions;
const positions = [-1, -1, 1, -1, -1, 1, 1, 1];
Expand All @@ -305,7 +315,8 @@ export default class IconLayer<DataT = any, ExtraPropsT extends {} = {}> extends
}
}
}),
isInstanced: true
isInstanced: true,
parameters
});
}

Expand All @@ -322,23 +333,17 @@ export default class IconLayer<DataT = any, ExtraPropsT extends {} = {}> extends
}
}

protected getInstanceOffset(icon: string): number[] {
protected getInstanceIconDef(icon: string): number[] {
const {
x,
y,
width,
height,
mask,
anchorX = width / 2,
anchorY = height / 2
} = this.state.iconManager.getIconMapping(icon);
return [width / 2 - anchorX, height / 2 - anchorY];
}

protected getInstanceColorMode(icon: string): number {
const mapping = this.state.iconManager.getIconMapping(icon);
return mapping.mask ? 1 : 0;
}

protected getInstanceIconFrame(icon: string): number[] {
const {x, y, width, height} = this.state.iconManager.getIconMapping(icon);
return [x, y, width, height];
return [width / 2 - anchorX, height / 2 - anchorY, x, y, width, height, mask ? 1 : 0];
}
}
129 changes: 129 additions & 0 deletions modules/layers/src/icon-layer/icon-layer.wgsl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

export const shaderWGSL = /* wgsl */ `\
struct IconUniforms {
sizeScale: f32,
iconsTextureDim: vec2<f32>,
sizeBasis: f32,
sizeMinPixels: f32,
sizeMaxPixels: f32,
billboard: i32,
sizeUnits: i32,
alphaCutoff: f32
};

@group(0) @binding(2) var<uniform> icon: IconUniforms;
@group(0) @binding(3) var iconsTexture : texture_2d<f32>;
@group(0) @binding(4) var iconsTextureSampler : sampler;

fn rotate_by_angle(vertex: vec2<f32>, angle_deg: f32) -> vec2<f32> {
let angle_radian = angle_deg * PI / 180.0;
let c = cos(angle_radian);
let s = sin(angle_radian);
let rotation = mat2x2<f32>(vec2<f32>(c, s), vec2<f32>(-s, c));
return rotation * vertex;
}

struct Attributes {
@location(0) positions: vec2<f32>,

@location(1) instancePositions: vec3<f32>,
@location(2) instancePositions64Low: vec3<f32>,
@location(3) instanceSizes: f32,
@location(4) instanceAngles: f32,
@location(5) instanceColors: vec4<f32>,
@location(6) instancePickingColors: vec3<f32>,
@location(7) instanceIconFrames: vec4<f32>,
@location(8) instanceColorModes: f32,
@location(9) instanceOffsets: vec2<f32>,
@location(10) instancePixelOffset: vec2<f32>,
};

struct Varyings {
@builtin(position) position: vec4<f32>,

@location(0) vColorMode: f32,
@location(1) vColor: vec4<f32>,
@location(2) vTextureCoords: vec2<f32>,
@location(3) uv: vec2<f32>,
};

@vertex
fn vertexMain(inp: Attributes) -> Varyings {
// write geometry fields used by filters + FS
geometry.worldPosition = inp.instancePositions;
geometry.uv = inp.positions;
geometry.pickingColor = inp.instancePickingColors;

var outp: Varyings;
outp.uv = inp.positions;

let iconSize = inp.instanceIconFrames.zw;

// convert size in meters to pixels, then clamp
let sizePixels = clamp(
project_unit_size_to_pixel(inp.instanceSizes * icon.sizeScale, icon.sizeUnits),
icon.sizeMinPixels, icon.sizeMaxPixels
);

// scale icon height to match instanceSize
let iconConstraint = select(iconSize.y, iconSize.x, icon.sizeBasis == 0.0);
let instanceScale = select(sizePixels / iconConstraint, 0.0, iconConstraint == 0.0);

// scale and rotate vertex in "pixel" units; then add per-instance pixel offset
var pixelOffset = inp.positions / 2.0 * iconSize + inp.instanceOffsets;
pixelOffset = rotate_by_angle(pixelOffset, inp.instanceAngles) * instanceScale;
pixelOffset = pixelOffset + inp.instancePixelOffset;
pixelOffset.y = pixelOffset.y * -1.0;

if (icon.billboard != 0) {
var pos = project_position_to_clipspace(inp.instancePositions, inp.instancePositions64Low, vec3<f32>(0.0)); // TODO, &geometry.position);
// DECKGL_FILTER_GL_POSITION(pos, geometry);

var offset = vec3<f32>(pixelOffset, 0.0);
// DECKGL_FILTER_SIZE(offset, geometry);
let clipOffset = project_pixel_size_to_clipspace(offset.xy);
pos = vec4<f32>(pos.x + clipOffset.x, pos.y + clipOffset.y, pos.z, pos.w);
outp.position = pos;
} else {
var offset_common = vec3<f32>(project_pixel_size_vec2(pixelOffset), 0.0);
// DECKGL_FILTER_SIZE(offset_common, geometry);
var pos = project_position_to_clipspace(inp.instancePositions, inp.instancePositions64Low, offset_common); // TODO, &geometry.position);
// DECKGL_FILTER_GL_POSITION(pos, geometry);
outp.position = pos;
}

let uvMix = (inp.positions.xy + vec2<f32>(1.0, 1.0)) * 0.5;
outp.vTextureCoords = mix(inp.instanceIconFrames.xy, inp.instanceIconFrames.xy + iconSize, uvMix) / icon.iconsTextureDim;

outp.vColor = inp.instanceColors;
// DECKGL_FILTER_COLOR(outp.vColor, geometry);

outp.vColorMode = inp.instanceColorModes;

return outp;
}

@fragment
fn fragmentMain(inp: Varyings) -> @location(0) vec4<f32> {
// expose to deck.gl filter hooks
geometry.uv = inp.uv;

let texColor = textureSample(iconsTexture, iconsTextureSampler, inp.vTextureCoords);

// if colorMode == 0, use pixel color from the texture
// if colorMode == 1 (or picking), use texture as transparency mask
let rgb = mix(texColor.rgb, inp.vColor.rgb, inp.vColorMode);
let a = texColor.a * color.opacity * inp.vColor.a;

if (a < icon.alphaCutoff) {
discard;
}

var fragColor = deckgl_premultiplied_alpha(vec4<f32>(rgb, a));
// DECKGL_FILTER_COLOR(fragColor, geometry);
return fragColor;
}
`;
16 changes: 6 additions & 10 deletions modules/layers/src/text-layer/multi-icon-layer/multi-icon-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,13 @@ export default class MultiIconLayer<DataT, ExtraPropsT extends {} = {}> extends
}
}

protected getInstanceOffset(icons: string): number[] {
return icons ? Array.from(icons).flatMap(icon => super.getInstanceOffset(icon)) : EMPTY_ARRAY;
}

getInstanceColorMode(icons: string): number {
return 1; // mask
}

getInstanceIconFrame(icons: string): number[] {
protected getInstanceIconDef(icons: string): number[] {
return icons
? Array.from(icons).flatMap(icon => super.getInstanceIconFrame(icon))
? Array.from(icons).flatMap(icon => {
const def = super.getInstanceIconDef(icon);
def[6] = 1; // mask
return def;
})
: EMPTY_ARRAY;
}
}
Loading