@deck.gl-community/layers
Version:
Add-on layers for deck.gl
179 lines • 6.29 kB
JavaScript
// deck.gl-community
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { PathLayer } from '@deck.gl/layers';
import { outline } from "./outline.js";
/**
* Unit literal to shader unit number conversion.
*/
export const UNIT = {
common: 0,
meters: 1,
pixels: 2
};
// TODO - this should be built into assembleShaders
function injectShaderCode({ source, code = '' }) {
const INJECT_CODE = /}[^{}]*$/;
return source.replace(INJECT_CODE, code.concat('\n}\n'));
}
const VS_CODE = `\
outline_setUV(gl_Position);
outline_setZLevel(instanceZLevel);
`;
const FS_CODE = `\
fragColor = outline_filterColor(fragColor);
`;
const OUTLINE_SHADOWMAP_PARAMETERS = {
blend: true,
blendColorSrcFactor: 'one',
blendColorDstFactor: 'one',
blendColorOperation: 'max',
blendAlphaSrcFactor: 'one',
blendAlphaDstFactor: 'one',
blendAlphaOperation: 'max',
depthWriteEnabled: false,
depthCompare: 'always'
};
const OUTLINE_RENDER_PARAMETERS = {
blend: false,
depthWriteEnabled: false,
depthCompare: 'always'
};
const defaultProps = {
getZLevel: () => 0
};
export class PathOutlineLayer extends PathLayer {
static layerName = 'PathOutlineLayer';
static defaultProps = defaultProps;
state = undefined;
// Override getShaders to inject the outline module
getShaders() {
const shaders = super.getShaders();
return Object.assign({}, shaders, {
modules: shaders.modules.concat([outline]),
vs: injectShaderCode({ source: shaders.vs, code: VS_CODE }),
fs: injectShaderCode({ source: shaders.fs, code: FS_CODE })
});
}
// @ts-expect-error PathLayer is missing LayerContext arg
initializeState(context) {
super.initializeState();
const attributeManager = this.getAttributeManager();
if (!attributeManager) {
throw new Error('PathOutlineLayer requires an attribute manager during initialization.');
}
// Create an outline "shadow" map
// TODO - we should create a single outlineMap for all layers
const outlineFramebuffer = context.device.createFramebuffer({
colorAttachments: [
context.device.createTexture({
format: 'rgba8unorm',
width: 1,
height: 1,
mipLevels: 1
})
]
});
const outlineEmptyTexture = context.device.createTexture({
format: 'rgba8unorm',
width: 1,
height: 1,
mipLevels: 1
});
attributeManager.addInstanced({
instanceZLevel: {
size: 1,
type: 'uint8',
accessor: 'getZLevel'
}
});
this.setState({
outlineFramebuffer,
outlineEmptyTexture,
model: this._getModel()
});
}
finalizeState(context) {
this.state.outlineFramebuffer?.destroy();
this.state.outlineEmptyTexture?.destroy();
super.finalizeState(context);
}
// Override draw to add render module
draw({ parameters = {} }) {
const model = this.state.model;
const outlineFramebuffer = this.state.outlineFramebuffer;
const outlineEmptyTexture = this.state.outlineEmptyTexture;
if (!model || !outlineFramebuffer || !outlineEmptyTexture) {
return;
}
const viewport = this.context.viewport;
const viewportWidth = Math.max(1, Math.ceil(viewport.width));
const viewportHeight = Math.max(1, Math.ceil(viewport.height));
outlineFramebuffer.resize({ width: viewportWidth, height: viewportHeight });
const shadowmapTexture = getFramebufferTexture(outlineFramebuffer);
if (!shadowmapTexture) {
return;
}
const { jointRounded, capRounded, billboard, miterLimit, widthUnits, widthScale, widthMinPixels, widthMaxPixels } = this.props;
const basePathProps = {
jointType: Number(jointRounded),
capType: Number(capRounded),
billboard,
widthUnits: UNIT[widthUnits],
widthScale,
miterLimit,
widthMinPixels,
widthMaxPixels
};
// Render the outline shadowmap (based on segment z orders)
this.setShaderModuleProps({
outline: {
outlineEnabled: true,
outlineRenderShadowmap: true,
outlineShadowmap: outlineEmptyTexture
}
});
model.shaderInputs.setProps({
path: {
...basePathProps,
jointType: 0,
widthScale: widthScale * 1.3
}
});
model.setParameters({ ...parameters, ...OUTLINE_SHADOWMAP_PARAMETERS });
const shadowRenderPass = this.context.device.beginRenderPass({
id: `${this.props.id}-outline-shadowmap`,
framebuffer: outlineFramebuffer,
parameters: { viewport: [0, 0, viewportWidth, viewportHeight] },
clearColor: [0, 0, 0, 0],
clearDepth: 1,
clearStencil: 0
});
model.draw(shadowRenderPass);
shadowRenderPass.end();
// Now use the outline shadowmap to render the lines (with outlines)
this.setShaderModuleProps({
outline: {
outlineEnabled: true,
outlineRenderShadowmap: false,
outlineShadowmap: shadowmapTexture
}
});
model.shaderInputs.setProps({
path: basePathProps
});
model.setParameters(isPickingPass(parameters) ? parameters : { ...parameters, ...OUTLINE_RENDER_PARAMETERS });
model.draw(this.context.renderPass);
}
}
function isPickingPass(parameters) {
return parameters.blend === true && parameters.blendAlphaSrcFactor === 'constant';
}
function getFramebufferTexture(framebuffer) {
const colorAttachment = framebuffer.colorAttachments[0];
if (!colorAttachment) {
return null;
}
return 'texture' in colorAttachment ? colorAttachment.texture : colorAttachment;
}
//# sourceMappingURL=path-outline-layer.js.map