UNPKG

8.59 kBPlain TextView Raw
1import StyleLayer from '../style_layer';
2import type Map from '../../ui/map';
3import assert from 'assert';
4import {mat4} from 'gl-matrix';
5import {LayerSpecification} from '../../style-spec/types.g';
6
7type CustomRenderMethod = (gl: WebGLRenderingContext, matrix: mat4) => void;
8
9/**
10 * Interface for custom style layers. This is a specification for
11 * implementers to model: it is not an exported method or class.
12 *
13 * Custom layers allow a user to render directly into the map's GL context using the map's camera.
14 * These layers can be added between any regular layers using {@link Map#addLayer}.
15 *
16 * Custom layers must have a unique `id` and must have the `type` of `"custom"`.
17 * They must implement `render` and may implement `prerender`, `onAdd` and `onRemove`.
18 * They can trigger rendering using {@link Map#triggerRepaint}
19 * and they should appropriately handle {@link Map.event:webglcontextlost} and
20 * {@link Map.event:webglcontextrestored}.
21 *
22 * The `renderingMode` property controls whether the layer is treated as a `"2d"` or `"3d"` map layer. Use:
23 * - `"renderingMode": "3d"` to use the depth buffer and share it with other layers
24 * - `"renderingMode": "2d"` to add a layer with no depth. If you need to use the depth buffer for a `"2d"` layer you must use an offscreen
25 * framebuffer and {@link CustomLayerInterface#prerender}
26 *
27 * @interface CustomLayerInterface
28 * @example
29 * // Custom layer implemented as ES6 class
30 * class NullIslandLayer {
31 * constructor() {
32 * this.id = 'null-island';
33 * this.type = 'custom';
34 * this.renderingMode = '2d';
35 * }
36 *
37 * onAdd(map, gl) {
38 * const vertexSource = `
39 * uniform mat4 u_matrix;
40 * void main() {
41 * gl_Position = u_matrix * vec4(0.5, 0.5, 0.0, 1.0);
42 * gl_PointSize = 20.0;
43 * }`;
44 *
45 * const fragmentSource = `
46 * void main() {
47 * gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
48 * }`;
49 *
50 * const vertexShader = gl.createShader(gl.VERTEX_SHADER);
51 * gl.shaderSource(vertexShader, vertexSource);
52 * gl.compileShader(vertexShader);
53 * const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
54 * gl.shaderSource(fragmentShader, fragmentSource);
55 * gl.compileShader(fragmentShader);
56 *
57 * this.program = gl.createProgram();
58 * gl.attachShader(this.program, vertexShader);
59 * gl.attachShader(this.program, fragmentShader);
60 * gl.linkProgram(this.program);
61 * }
62 *
63 * render(gl, matrix) {
64 * gl.useProgram(this.program);
65 * gl.uniformMatrix4fv(gl.getUniformLocation(this.program, "u_matrix"), false, matrix);
66 * gl.drawArrays(gl.POINTS, 0, 1);
67 * }
68 * }
69 *
70 * map.on('load', function() {
71 * map.addLayer(new NullIslandLayer());
72 * });
73 */
74export interface CustomLayerInterface {
75 /**
76 * @property {string} id A unique layer id.
77 */
78 id: string;
79 /**
80 * @property {string} type The layer's type. Must be `"custom"`.
81 */
82 type: 'custom';
83 /**
84 * @property {string} renderingMode Either `"2d"` or `"3d"`. Defaults to `"2d"`.
85 */
86 renderingMode?: '2d' | '3d';
87 /**
88 * Called during a render frame allowing the layer to draw into the GL context.
89 *
90 * The layer can assume blending and depth state is set to allow the layer to properly
91 * blend and clip other layers. The layer cannot make any other assumptions about the
92 * current GL state.
93 *
94 * If the layer needs to render to a texture, it should implement the `prerender` method
95 * to do this and only use the `render` method for drawing directly into the main framebuffer.
96 *
97 * The blend function is set to `gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)`. This expects
98 * colors to be provided in premultiplied alpha form where the `r`, `g` and `b` values are already
99 * multiplied by the `a` value. If you are unable to provide colors in premultiplied form you
100 * may want to change the blend function to
101 * `gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA)`.
102 *
103 * @function
104 * @memberof CustomLayerInterface
105 * @instance
106 * @name render
107 * @param {WebGLRenderingContext} gl The map's gl context.
108 * @param {Array<number>} matrix The map's camera matrix. It projects spherical mercator
109 * coordinates to gl coordinates. The spherical mercator coordinate `[0, 0]` represents the
110 * top left corner of the mercator world and `[1, 1]` represents the bottom right corner. When
111 * the `renderingMode` is `"3d"`, the z coordinate is conformal. A box with identical x, y, and z
112 * lengths in mercator units would be rendered as a cube. {@link MercatorCoordinate}.fromLngLat
113 * can be used to project a `LngLat` to a mercator coordinate.
114 */
115 render: CustomRenderMethod;
116 /**
117 * Optional method called during a render frame to allow a layer to prepare resources or render into a texture.
118 *
119 * The layer cannot make any assumptions about the current GL state and must bind a framebuffer before rendering.
120 *
121 * @function
122 * @memberof CustomLayerInterface
123 * @instance
124 * @name prerender
125 * @param {WebGLRenderingContext} gl The map's gl context.
126 * @param {mat4} matrix The map's camera matrix. It projects spherical mercator
127 * coordinates to gl coordinates. The mercator coordinate `[0, 0]` represents the
128 * top left corner of the mercator world and `[1, 1]` represents the bottom right corner. When
129 * the `renderingMode` is `"3d"`, the z coordinate is conformal. A box with identical x, y, and z
130 * lengths in mercator units would be rendered as a cube. {@link MercatorCoordinate}.fromLngLat
131 * can be used to project a `LngLat` to a mercator coordinate.
132 */
133 prerender?: CustomRenderMethod;
134 /**
135 * Optional method called when the layer has been added to the Map with {@link Map#addLayer}. This
136 * gives the layer a chance to initialize gl resources and register event listeners.
137 *
138 * @function
139 * @memberof CustomLayerInterface
140 * @instance
141 * @name onAdd
142 * @param {Map} map The Map this custom layer was just added to.
143 * @param {WebGLRenderingContext} gl The gl context for the map.
144 */
145 onAdd?(map: Map, gl: WebGLRenderingContext): void;
146 /**
147 * Optional method called when the layer has been removed from the Map with {@link Map#removeLayer}. This
148 * gives the layer a chance to clean up gl resources and event listeners.
149 *
150 * @function
151 * @memberof CustomLayerInterface
152 * @instance
153 * @name onRemove
154 * @param {Map} map The Map this custom layer was just added to.
155 * @param {WebGLRenderingContext} gl The gl context for the map.
156 */
157 onRemove?(map: Map, gl: WebGLRenderingContext): void;
158}
159
160export function validateCustomStyleLayer(layerObject: CustomLayerInterface) {
161 const errors = [];
162 const id = layerObject.id;
163
164 if (id === undefined) {
165 errors.push({
166 message: `layers.${id}: missing required property "id"`
167 });
168 }
169
170 if (layerObject.render === undefined) {
171 errors.push({
172 message: `layers.${id}: missing required method "render"`
173 });
174 }
175
176 if (layerObject.renderingMode &&
177 layerObject.renderingMode !== '2d' &&
178 layerObject.renderingMode !== '3d') {
179 errors.push({
180 message: `layers.${id}: property "renderingMode" must be either "2d" or "3d"`
181 });
182 }
183
184 return errors;
185}
186
187class CustomStyleLayer extends StyleLayer {
188
189 implementation: CustomLayerInterface;
190
191 constructor(implementation: CustomLayerInterface) {
192 super(implementation, {});
193 this.implementation = implementation;
194 }
195
196 is3D() {
197 return this.implementation.renderingMode === '3d';
198 }
199
200 hasOffscreenPass() {
201 return this.implementation.prerender !== undefined;
202 }
203
204 recalculate() {}
205 updateTransitions() {}
206 hasTransition() { return false; }
207
208 serialize(): LayerSpecification {
209 assert(false, 'Custom layers cannot be serialized');
210 }
211
212 onAdd = (map: Map) => {
213 if (this.implementation.onAdd) {
214 this.implementation.onAdd(map, map.painter.context.gl);
215 }
216 };
217
218 onRemove = (map: Map) => {
219 if (this.implementation.onRemove) {
220 this.implementation.onRemove(map, map.painter.context.gl);
221 }
222 };
223}
224
225export default CustomStyleLayer;