dev-docs/RFCs/v7.2/layer-extension-rfc.md
This is a proposal for adding/removing optional functionalities to a deck.gl layer on-demand.
As the deck.gl layers ecosystem grows, we see many feature requests as variations of some existing layer. One of deck.gl's core design goals is easily composible and extensible layers. Over the last few major releases we introduced features such as Composite Layer, shader modules, shader injection, etc. While they offer much flexibility to authors of custom layers, it is not quite accessible to the majority of our users.
To begin with, adding a feature to an existing core layer is no easy task. It usually involves modification to multiple steps in the layer lifecycle, e.g. adding new props, uniforms, declaring and generating new attributes, and injecting code into the shaders. It requires the author to understand the general WebGL render pipeline and GLSL, deck.gl's layer lifecycle and attribute management system, and sometimes luma.gl. This is outlined in our tutorial for subclassed layers.
Secondly, because of the ad-hoc nature of layer customization, it is difficult to package and reuse an "extension" for other layers. This is apprent in the kepler.gl repo where a custom version of every core layer must be created in order to override a simple default bahavior.
To mitigate this, we can of course add as many customizable functionalities to the base layer as possible. However, it is a major concern that inflating the base layer will sacrefice performance in return. Additional attributes takes CPU cycles to generate and memory to store. Larger shaders take longer to compile. WebGL1 has a restricting limit of 16 attributes per vertex shader, so adding attributes without the user asking for it will make the layer less extensible instead.
project64 (replaces project32)See Data Filter RFC
Components:
getFilterValue, filterRangefilterinstanceFilterValuefilter_setVisibility(instanceFilterValue);gl_FragColor = filter_filterColor(gl_FragColor);A generic version of this functionality would need the following components:
enableBrush, brushRadiusbrushingbrush_setVisibility(instancePositions);gl_FragColor = brush_filterColor(gl_FragColor);Some changes to the base Layer class are needed to make a generic, reusable extension system work:
getShaders, createModels that each layer must implement.bufferLayout support in attribute management. The bufferLayout state is used in PathLayer and PolygonLayer to describe the number of instance for each data object. Currently, AttributeManager's auto-update feature always assumes 1:1 mapping between an instance and a data object. Without supporting variable layout, an extension will have to implement layer-specific updaters for these layers.Add a LayerExtension interface. All layer extensions should extend this class. It contains the following methods:
getShaders(shaders) - called after a layer's own getShaders, a hook to inject additional modules/code into the shadersinitializeState(context, layer) - called after a layer's own initializeState, a hook to add attributes and/or initial states.
context (Object) - same object passed to layer.initializeState.layer (Layer) - the parent layer.updateState(params, layer) - called after a layer's own updateState, a hook to update layer state from props.
params (Object) - same object passed to layer.updateState.layer (Layer) - the parent layer.finalizeState(layer) - called after a layer's own finalizeState, a hook to clean up resources
layer (Layer) - the parent layer.Add a new extensions prop to the base Layer class which accepts an array of LayerExtension objects.
import {Deck} from '@deck.gl/core';
import {ScatterplotLayer} from '@deck.gl/layers';
import {Brushing, DataFilter} from '@deck.gl/layer-extensions';
const LAYER_EXTENSIONS = [
new Brushing(),
new DataFilter({size: 2}) // with options
];
new Deck({
layers: [
new ScatterplotLayer({
extensions: LAYER_EXTENSIONS,
// props for brushing
enableBrush: false,
// props for filtering
getFilterValue: d => [d.time, d.count],
filterRange: [1545000000, 1545002000, 10, 20],
...
})
]
})
The default value of extensions is [].
Pros:
Challenges:
This is inspired by React's Higher-Order Components concept.
import {Deck} from '@deck.gl/core';
import {ScatterplotLayer} from '@deck.gl/layers';
import {brushing, filterable} from '@deck.gl/layer-extensions';
const BrushingFiterableScatterplotLayer = brushing(filterable(ScatterplotLayer, {size: 2}));
new Deck({
layers: [
new BrushingFiterableScatterplotLayer({
// props for brushing
enableBrush: false,
// props for filtering
getFilterValue: d => [d.time, d.count],
filterRange: [1545000000, 1545002000, 10, 20],
...
})
]
});
When used, these extensions wrap the original layer and return a new class with the additional functionality.
Pros:
Cons:
subLayerProps.