@deck.gl/extensions
Version:
Plug-and-play functionalities for deck.gl layers
311 lines (277 loc) • 8.7 kB
text/typescript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import type {ShaderModule} from '@luma.gl/shadertools';
import type {DataFilterExtensionOptions, DataFilterExtensionProps} from './data-filter-extension';
import {UniformFormat} from '@luma.gl/shadertools/dist/types';
/*
* data filter shader module
*/
export type Defines = {
// Defines passed externally
/**
* Primitive type of parameter used for category filtering. If undefined, category filtering disabled.
*/
DATACATEGORY_TYPE?: 'uint' | 'uvec2' | 'uvec3' | 'uvec4';
/**
* Number of category filtering channels. Must match dimension of `DATACATEGORY_TYPE`
*/
DATACATEGORY_CHANNELS?: 1 | 2 | 3 | 4;
/**
* Primitive type of parameter used for numeric filtering. If undefined, numeric filtering disabled.
*/
DATAFILTER_TYPE?: 'float' | 'vec2' | 'vec3' | 'vec4';
/**
* Enable 64-bit precision in numeric filter.
*/
DATAFILTER_DOUBLE?: boolean;
};
const uniformBlock = /* glsl */ `\
uniform dataFilterUniforms {
bool useSoftMargin;
bool enabled;
bool transformSize;
bool transformColor;
DATAFILTER_TYPE min;
DATAFILTER_TYPE softMin;
DATAFILTER_TYPE softMax;
DATAFILTER_TYPE max;
DATAFILTER_TYPE min64High;
DATAFILTER_TYPE max64High;
highp uvec4 categoryBitMask;
} dataFilter;
`;
const vertex = /* glsl */ `
in DATAFILTER_TYPE filterValues;
in DATAFILTER_TYPE filterValues64Low;
in DATACATEGORY_TYPE filterCategoryValues;
out float dataFilter_value;
float dataFilter_reduceValue(float value) {
return value;
}
float dataFilter_reduceValue(vec2 value) {
return min(value.x, value.y);
}
float dataFilter_reduceValue(vec3 value) {
return min(min(value.x, value.y), value.z);
}
float dataFilter_reduceValue(vec4 value) {
return min(min(value.x, value.y), min(value.z, value.w));
}
void dataFilter_setValue(DATAFILTER_TYPE valueFromMin, DATAFILTER_TYPE valueFromMax) {
if (dataFilter.useSoftMargin) {
// smoothstep results are undefined if edge0 ≥ edge1
// Fallback to ignore filterSoftRange if it is truncated by filterRange
DATAFILTER_TYPE leftInRange = mix(
smoothstep(dataFilter.min, dataFilter.softMin, valueFromMin),
step(dataFilter.min, valueFromMin),
step(dataFilter.softMin, dataFilter.min)
);
DATAFILTER_TYPE rightInRange = mix(
1.0 - smoothstep(dataFilter.softMax, dataFilter.max, valueFromMax),
step(valueFromMax, dataFilter.max),
step(dataFilter.max, dataFilter.softMax)
);
dataFilter_value = dataFilter_reduceValue(leftInRange * rightInRange);
} else {
dataFilter_value = dataFilter_reduceValue(
step(dataFilter.min, valueFromMin) * step(valueFromMax, dataFilter.max)
);
}
}
void dataFilter_setCategoryValue(DATACATEGORY_TYPE category) {
uint dataFilter_masks = dataFilter.categoryBitMask[category / 32u];
uvec2 dataFilter_masks = uvec2(
dataFilter.categoryBitMask[category.x / 32u],
dataFilter.categoryBitMask[category.y / 32u + 2u]
);
uvec3 dataFilter_masks = dataFilter.categoryBitMask.xyz;
uvec4 dataFilter_masks = dataFilter.categoryBitMask;
// Shift mask and extract relevant bits
DATACATEGORY_TYPE dataFilter_bits = DATACATEGORY_TYPE(dataFilter_masks) >> (category & 31u);
dataFilter_bits &= 1u;
if(dataFilter_bits == 0u) dataFilter_value = 0.0;
if(any(equal(dataFilter_bits, DATACATEGORY_TYPE(0u)))) dataFilter_value = 0.0;
}
`;
const vs = `
${uniformBlock}
${vertex}
`;
const fragment = /* glsl */ `
in float dataFilter_value;
`;
const fs = `
${uniformBlock}
${fragment}
`;
export type CategoryBitMask = Uint32Array;
export type DataFilterModuleProps = {
extensions: any[]; // used to detect if layer props are present
categoryBitMask?: CategoryBitMask;
} & DataFilterExtensionProps;
/* eslint-disable camelcase */
function getUniforms(opts?: DataFilterModuleProps | {}): Record<string, any> {
if (!opts || !('extensions' in opts)) {
return {};
}
const {
filterRange = [-1, 1],
filterEnabled = true,
filterTransformSize = true,
filterTransformColor = true,
categoryBitMask
} = opts;
const filterSoftRange = opts.filterSoftRange || filterRange;
return {
...(Number.isFinite(filterRange[0])
? {
min: filterRange[0],
softMin: filterSoftRange[0],
softMax: filterSoftRange[1],
max: filterRange[1]
}
: {
min: filterRange.map(r => r[0]),
softMin: filterSoftRange.map(r => r[0]),
softMax: filterSoftRange.map(r => r[1]),
max: filterRange.map(r => r[1])
}),
enabled: filterEnabled,
useSoftMargin: Boolean(opts.filterSoftRange),
transformSize: filterEnabled && filterTransformSize,
transformColor: filterEnabled && filterTransformColor,
...(categoryBitMask && {categoryBitMask})
};
}
function getUniforms64(opts?: DataFilterModuleProps | {}): Record<string, any> {
if (!opts || !('extensions' in opts)) {
return {};
}
const uniforms = getUniforms(opts);
if (Number.isFinite(uniforms.min)) {
const min64High = Math.fround(uniforms.min);
uniforms.min -= min64High;
uniforms.softMin -= min64High;
uniforms.min64High = min64High;
const max64High = Math.fround(uniforms.max);
uniforms.max -= max64High;
uniforms.softMax -= max64High;
uniforms.max64High = max64High;
} else {
const min64High = uniforms.min.map(Math.fround);
uniforms.min = uniforms.min.map((x, i) => x - min64High[i]);
uniforms.softMin = uniforms.softMin.map((x, i) => x - min64High[i]);
uniforms.min64High = min64High;
const max64High = uniforms.max.map(Math.fround);
uniforms.max = uniforms.max.map((x, i) => x - max64High[i]);
uniforms.softMax = uniforms.softMax.map((x, i) => x - max64High[i]);
uniforms.max64High = max64High;
}
return uniforms;
}
const inject = {
'vs:#main-start': /* glsl */ `
dataFilter_value = 1.0;
if (dataFilter.enabled) {
dataFilter_setValue(
filterValues - dataFilter.min64High + filterValues64Low,
filterValues - dataFilter.max64High + filterValues64Low
);
dataFilter_setValue(filterValues, filterValues);
dataFilter_setCategoryValue(filterCategoryValues);
}
`,
'vs:#main-end': /* glsl */ `
if (dataFilter_value == 0.0) {
gl_Position = vec4(0.);
}
`,
'vs:DECKGL_FILTER_SIZE': /* glsl */ `
if (dataFilter.transformSize) {
size = size * dataFilter_value;
}
`,
'fs:DECKGL_FILTER_COLOR': /* glsl */ `
if (dataFilter_value == 0.0) discard;
if (dataFilter.transformColor) {
color.a *= dataFilter_value;
}
`
};
type UniformTypesFunc = (opts: DataFilterExtensionOptions) => any;
function uniformTypesFromOptions(opts: DataFilterExtensionOptions): any {
const {categorySize, filterSize, fp64} = opts;
const uniformTypes: Record<string, UniformFormat> = {
useSoftMargin: 'i32',
enabled: 'i32',
transformSize: 'i32',
transformColor: 'i32'
};
if (filterSize) {
const uniformFormat: UniformFormat = filterSize === 1 ? 'f32' : `vec${filterSize}<f32>`;
uniformTypes.min = uniformFormat;
uniformTypes.softMin = uniformFormat;
uniformTypes.softMax = uniformFormat;
uniformTypes.max = uniformFormat;
if (fp64) {
uniformTypes.min64High = uniformFormat;
uniformTypes.max64High = uniformFormat;
}
}
if (categorySize) {
uniformTypes.categoryBitMask = 'vec4<i32>';
}
return uniformTypes;
}
export const dataFilter: ShaderModule<DataFilterModuleProps> & {
uniformTypesFromOptions: UniformTypesFunc;
} = {
name: 'dataFilter',
vs,
fs,
inject,
getUniforms,
uniformTypesFromOptions
};
export const dataFilter64: ShaderModule<DataFilterModuleProps> & {
uniformTypesFromOptions: UniformTypesFunc;
} = {
name: 'dataFilter',
vs,
fs,
inject,
getUniforms: getUniforms64,
uniformTypesFromOptions
};