Merge pull request #13461 from jahow/webgl-shape-renderer
WebGL vector renderer for polygons, lines and points
This commit is contained in:
@@ -13,26 +13,6 @@ import {
|
||||
} from '../../transform.js';
|
||||
import {containsCoordinate} from '../../extent.js';
|
||||
|
||||
/**
|
||||
* @enum {string}
|
||||
*/
|
||||
export const WebGLWorkerMessageType = {
|
||||
GENERATE_BUFFERS: 'GENERATE_BUFFERS',
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {Object} WebGLWorkerGenerateBuffersMessage
|
||||
* This message will trigger the generation of a vertex and an index buffer based on the given render instructions.
|
||||
* When the buffers are generated, the worked will send a message of the same type to the main thread, with
|
||||
* the generated buffers in it.
|
||||
* Note that any addition properties present in the message *will* be sent back to the main thread.
|
||||
* @property {WebGLWorkerMessageType} type Message type
|
||||
* @property {ArrayBuffer} renderInstructions Render instructions raw binary buffer.
|
||||
* @property {ArrayBuffer} [vertexBuffer] Vertices array raw binary buffer (sent by the worker).
|
||||
* @property {ArrayBuffer} [indexBuffer] Indices array raw binary buffer (sent by the worker).
|
||||
* @property {number} [customAttributesCount] Amount of custom attributes count in the render instructions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} PostProcessesOptions
|
||||
* @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than
|
||||
@@ -343,144 +323,4 @@ class WebGLLayerRenderer extends LayerRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
const tmpArray_ = [];
|
||||
const bufferPositions_ = {vertexPosition: 0, indexPosition: 0};
|
||||
|
||||
function writePointVertex(buffer, pos, x, y, index) {
|
||||
buffer[pos + 0] = x;
|
||||
buffer[pos + 1] = y;
|
||||
buffer[pos + 2] = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object holding positions both in an index and a vertex buffer.
|
||||
* @typedef {Object} BufferPositions
|
||||
* @property {number} vertexPosition Position in the vertex buffer
|
||||
* @property {number} indexPosition Position in the index buffer
|
||||
*/
|
||||
|
||||
/**
|
||||
* Pushes a quad (two triangles) based on a point geometry
|
||||
* @param {Float32Array} instructions Array of render instructions for points.
|
||||
* @param {number} elementIndex Index from which render instructions will be read.
|
||||
* @param {Float32Array} vertexBuffer Buffer in the form of a typed array.
|
||||
* @param {Uint32Array} indexBuffer Buffer in the form of a typed array.
|
||||
* @param {number} customAttributesCount Amount of custom attributes for each element.
|
||||
* @param {BufferPositions} [bufferPositions] Buffer write positions; if not specified, positions will be set at 0.
|
||||
* @return {BufferPositions} New buffer positions where to write next
|
||||
* @property {number} vertexPosition New position in the vertex buffer where future writes should start.
|
||||
* @property {number} indexPosition New position in the index buffer where future writes should start.
|
||||
* @private
|
||||
*/
|
||||
export function writePointFeatureToBuffers(
|
||||
instructions,
|
||||
elementIndex,
|
||||
vertexBuffer,
|
||||
indexBuffer,
|
||||
customAttributesCount,
|
||||
bufferPositions
|
||||
) {
|
||||
// This is for x, y and index
|
||||
const baseVertexAttrsCount = 3;
|
||||
const baseInstructionsCount = 2;
|
||||
const stride = baseVertexAttrsCount + customAttributesCount;
|
||||
|
||||
const x = instructions[elementIndex + 0];
|
||||
const y = instructions[elementIndex + 1];
|
||||
|
||||
// read custom numerical attributes on the feature
|
||||
const customAttrs = tmpArray_;
|
||||
customAttrs.length = customAttributesCount;
|
||||
for (let i = 0; i < customAttrs.length; i++) {
|
||||
customAttrs[i] = instructions[elementIndex + baseInstructionsCount + i];
|
||||
}
|
||||
|
||||
let vPos = bufferPositions ? bufferPositions.vertexPosition : 0;
|
||||
let iPos = bufferPositions ? bufferPositions.indexPosition : 0;
|
||||
const baseIndex = vPos / stride;
|
||||
|
||||
// push vertices for each of the four quad corners (first standard then custom attributes)
|
||||
writePointVertex(vertexBuffer, vPos, x, y, 0);
|
||||
customAttrs.length &&
|
||||
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||
vPos += stride;
|
||||
|
||||
writePointVertex(vertexBuffer, vPos, x, y, 1);
|
||||
customAttrs.length &&
|
||||
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||
vPos += stride;
|
||||
|
||||
writePointVertex(vertexBuffer, vPos, x, y, 2);
|
||||
customAttrs.length &&
|
||||
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||
vPos += stride;
|
||||
|
||||
writePointVertex(vertexBuffer, vPos, x, y, 3);
|
||||
customAttrs.length &&
|
||||
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||
vPos += stride;
|
||||
|
||||
indexBuffer[iPos++] = baseIndex;
|
||||
indexBuffer[iPos++] = baseIndex + 1;
|
||||
indexBuffer[iPos++] = baseIndex + 3;
|
||||
indexBuffer[iPos++] = baseIndex + 1;
|
||||
indexBuffer[iPos++] = baseIndex + 2;
|
||||
indexBuffer[iPos++] = baseIndex + 3;
|
||||
|
||||
bufferPositions_.vertexPosition = vPos;
|
||||
bufferPositions_.indexPosition = iPos;
|
||||
|
||||
return bufferPositions_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a texture of 1x1 pixel, white
|
||||
* @private
|
||||
* @return {ImageData} Image data.
|
||||
*/
|
||||
export function getBlankImageData() {
|
||||
const canvas = document.createElement('canvas');
|
||||
const image = canvas.getContext('2d').createImageData(1, 1);
|
||||
image.data[0] = 255;
|
||||
image.data[1] = 255;
|
||||
image.data[2] = 255;
|
||||
image.data[3] = 255;
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a color array based on a numerical id
|
||||
* Note: the range for each component is 0 to 1 with 256 steps
|
||||
* @param {number} id Id
|
||||
* @param {Array<number>} [opt_array] Reusable array
|
||||
* @return {Array<number>} Color array containing the encoded id
|
||||
*/
|
||||
export function colorEncodeId(id, opt_array) {
|
||||
const array = opt_array || [];
|
||||
const radix = 256;
|
||||
const divide = radix - 1;
|
||||
array[0] = Math.floor(id / radix / radix / radix) / divide;
|
||||
array[1] = (Math.floor(id / radix / radix) % radix) / divide;
|
||||
array[2] = (Math.floor(id / radix) % radix) / divide;
|
||||
array[3] = (id % radix) / divide;
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an id from a color-encoded array
|
||||
* Note: the expected range for each component is 0 to 1 with 256 steps.
|
||||
* @param {Array<number>} color Color array containing the encoded id
|
||||
* @return {number} Decoded id
|
||||
*/
|
||||
export function colorDecodeId(color) {
|
||||
let id = 0;
|
||||
const radix = 256;
|
||||
const mult = radix - 1;
|
||||
id += Math.round(color[0] * radix * radix * radix * mult);
|
||||
id += Math.round(color[1] * radix * radix * mult);
|
||||
id += Math.round(color[2] * radix * mult);
|
||||
id += Math.round(color[3] * mult);
|
||||
return id;
|
||||
}
|
||||
|
||||
export default WebGLLayerRenderer;
|
||||
|
||||
@@ -5,14 +5,11 @@ import BaseVector from '../../layer/BaseVector.js';
|
||||
import VectorEventType from '../../source/VectorEventType.js';
|
||||
import ViewHint from '../../ViewHint.js';
|
||||
import WebGLArrayBuffer from '../../webgl/Buffer.js';
|
||||
import WebGLLayerRenderer, {
|
||||
WebGLWorkerMessageType,
|
||||
colorDecodeId,
|
||||
colorEncodeId,
|
||||
} from './Layer.js';
|
||||
import WebGLLayerRenderer from './Layer.js';
|
||||
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
|
||||
import {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js';
|
||||
import {AttributeType, DefaultUniform} from '../../webgl/Helper.js';
|
||||
import {WebGLWorkerMessageType} from '../../render/webgl/constants.js';
|
||||
import {
|
||||
apply as applyTransform,
|
||||
create as createTransform,
|
||||
@@ -22,6 +19,7 @@ import {
|
||||
} from '../../transform.js';
|
||||
import {assert} from '../../asserts.js';
|
||||
import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
|
||||
import {colorDecodeId, colorEncodeId} from '../../render/webgl/utils.js';
|
||||
import {create as createWebGLWorker} from '../../worker/webgl.js';
|
||||
import {getUid} from '../../util.js';
|
||||
import {listen, unlistenByKey} from '../../events.js';
|
||||
@@ -294,7 +292,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
*/
|
||||
this.generateBuffersRun_ = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.worker_ = createWebGLWorker();
|
||||
|
||||
this.worker_.addEventListener(
|
||||
'message',
|
||||
/**
|
||||
@@ -303,7 +305,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
*/
|
||||
function (event) {
|
||||
const received = event.data;
|
||||
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||
if (received.type === WebGLWorkerMessageType.GENERATE_POINT_BUFFERS) {
|
||||
const projectionTransform = received.projectionTransform;
|
||||
if (received.hitDetection) {
|
||||
this.hitVerticesBuffer_.fromArrayBuffer(received.vertexBuffer);
|
||||
@@ -540,7 +542,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
this.previousExtent_ = frameState.extent.slice();
|
||||
}
|
||||
|
||||
this.helper.useProgram(this.program_);
|
||||
this.helper.useProgram(this.program_, frameState);
|
||||
this.helper.prepareDraw(frameState);
|
||||
|
||||
// write new data
|
||||
@@ -637,9 +639,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
|
||||
/** @type {import('../../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||
const message = {
|
||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||
id: 0,
|
||||
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
|
||||
renderInstructions: this.renderInstructions_.buffer,
|
||||
customAttributesCount: this.customAttributes.length,
|
||||
};
|
||||
@@ -650,10 +653,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
this.worker_.postMessage(message, [this.renderInstructions_.buffer]);
|
||||
this.renderInstructions_ = null;
|
||||
|
||||
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
|
||||
/** @type {import('../../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||
if (this.hitDetectionEnabled_) {
|
||||
const hitMessage = {
|
||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||
id: 0,
|
||||
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
|
||||
renderInstructions: this.hitRenderInstructions_.buffer,
|
||||
customAttributesCount: 5 + this.customAttributes.length,
|
||||
};
|
||||
@@ -726,7 +730,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
Math.floor(frameState.size[1] / 2),
|
||||
]);
|
||||
|
||||
this.helper.useProgram(this.hitProgram_);
|
||||
this.helper.useProgram(this.hitProgram_, frameState);
|
||||
this.helper.prepareDrawToRenderTarget(
|
||||
frameState,
|
||||
this.hitRenderTarget_,
|
||||
|
||||
@@ -508,7 +508,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
this.helper.useProgram(this.program_);
|
||||
this.helper.useProgram(this.program_, frameState);
|
||||
this.helper.prepareDraw(frameState, !blend);
|
||||
|
||||
const zs = Object.keys(tileTexturesByZ)
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* @module ol/renderer/webgl/VectorLayer
|
||||
*/
|
||||
import BaseVector from '../../layer/BaseVector.js';
|
||||
import LineStringBatchRenderer from '../../render/webgl/LineStringBatchRenderer.js';
|
||||
import MixedGeometryBatch from '../../render/webgl/MixedGeometryBatch.js';
|
||||
import PointBatchRenderer from '../../render/webgl/PointBatchRenderer.js';
|
||||
import PolygonBatchRenderer from '../../render/webgl/PolygonBatchRenderer.js';
|
||||
import VectorEventType from '../../source/VectorEventType.js';
|
||||
import ViewHint from '../../ViewHint.js';
|
||||
import WebGLLayerRenderer from './Layer.js';
|
||||
import {DefaultUniform} from '../../webgl/Helper.js';
|
||||
import {
|
||||
FILL_FRAGMENT_SHADER,
|
||||
FILL_VERTEX_SHADER,
|
||||
POINT_FRAGMENT_SHADER,
|
||||
POINT_VERTEX_SHADER,
|
||||
STROKE_FRAGMENT_SHADER,
|
||||
STROKE_VERTEX_SHADER,
|
||||
packColor,
|
||||
} from './shaders.js';
|
||||
import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
|
||||
import {create as createTransform} from '../../transform.js';
|
||||
import {create as createWebGLWorker} from '../../worker/webgl.js';
|
||||
import {listen, unlistenByKey} from '../../events.js';
|
||||
|
||||
/**
|
||||
* @typedef {function(import("../../Feature").default, Object<string, *>):number} CustomAttributeCallback A callback computing
|
||||
* the value of a custom attribute (different for each feature) to be passed on to the GPU.
|
||||
* Properties are available as 2nd arg for quicker access.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ShaderProgram An object containing both shaders (vertex and fragment) as well as the required attributes
|
||||
* @property {string} [vertexShader] Vertex shader source (using the default one if unspecified).
|
||||
* @property {string} [fragmentShader] Fragment shader source (using the default one if unspecified).
|
||||
* @property {Object<import("./shaders.js").DefaultAttributes,CustomAttributeCallback>} attributes Custom attributes made available in the vertex shader.
|
||||
* Keys are the names of the attributes which are then accessible in the vertex shader using the `a_` prefix, e.g.: `a_opacity`.
|
||||
* Default shaders rely on the attributes in {@link module:ol/render/webgl/shaders~DefaultAttributes}.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Options
|
||||
* @property {string} [className='ol-layer'] A CSS class name to set to the canvas element.
|
||||
* @property {ShaderProgram} [fill] Attributes and shaders for filling polygons.
|
||||
* @property {ShaderProgram} [stroke] Attributes and shaders for line strings and polygon strokes.
|
||||
* @property {ShaderProgram} [point] Attributes and shaders for points.
|
||||
* @property {Object<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions.
|
||||
* @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Object<import("./shaders.js").DefaultAttributes,CustomAttributeCallback>} obj Lookup of attribute getters.
|
||||
* @return {Array<import("../../render/webgl/BatchRenderer").CustomAttribute>} An array of attribute descriptors.
|
||||
*/
|
||||
function toAttributesArray(obj) {
|
||||
return Object.keys(obj).map((key) => ({name: key, callback: obj[key]}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Experimental WebGL vector renderer. Supports polygons, lines and points:
|
||||
* * Polygons are broken down into triangles
|
||||
* * Lines are rendered as strips of quads
|
||||
* * Points are rendered as quads
|
||||
*
|
||||
* You need to provide vertex and fragment shaders as well as custom attributes for each type of geometry. All shaders
|
||||
* can access the uniforms in the {@link module:ol/webgl/Helper~DefaultUniform} enum.
|
||||
* The vertex shaders can access the following attributes depending on the geometry type:
|
||||
* * For polygons: {@link module:ol/render/webgl/PolygonBatchRenderer~Attributes}
|
||||
* * For line strings: {@link module:ol/render/webgl/LineStringBatchRenderer~Attributes}
|
||||
* * For points: {@link module:ol/render/webgl/PointBatchRenderer~Attributes}
|
||||
*
|
||||
* Please note that the fragment shaders output should have premultiplied alpha, otherwise visual anomalies may occur.
|
||||
*
|
||||
* Note: this uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
|
||||
*/
|
||||
class WebGLVectorLayerRenderer extends WebGLLayerRenderer {
|
||||
/**
|
||||
* @param {import("../../layer/Layer.js").default} layer Layer.
|
||||
* @param {Options} options Options.
|
||||
*/
|
||||
constructor(layer, options) {
|
||||
const uniforms = options.uniforms || {};
|
||||
const projectionMatrixTransform = createTransform();
|
||||
uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
|
||||
|
||||
super(layer, {
|
||||
uniforms: uniforms,
|
||||
postProcesses: options.postProcesses,
|
||||
});
|
||||
|
||||
this.sourceRevision_ = -1;
|
||||
|
||||
this.previousExtent_ = createEmpty();
|
||||
|
||||
/**
|
||||
* This transform is updated on every frame and is the composition of:
|
||||
* - invert of the world->screen transform that was used when rebuilding buffers (see `this.renderTransform_`)
|
||||
* - current world->screen transform
|
||||
* @type {import("../../transform.js").Transform}
|
||||
* @private
|
||||
*/
|
||||
this.currentTransform_ = projectionMatrixTransform;
|
||||
|
||||
const fillAttributes = {
|
||||
color: function () {
|
||||
return packColor('#ddd');
|
||||
},
|
||||
opacity: function () {
|
||||
return 1;
|
||||
},
|
||||
...(options.fill && options.fill.attributes),
|
||||
};
|
||||
|
||||
const strokeAttributes = {
|
||||
color: function () {
|
||||
return packColor('#eee');
|
||||
},
|
||||
opacity: function () {
|
||||
return 1;
|
||||
},
|
||||
width: function () {
|
||||
return 1.5;
|
||||
},
|
||||
...(options.stroke && options.stroke.attributes),
|
||||
};
|
||||
|
||||
const pointAttributes = {
|
||||
color: function () {
|
||||
return packColor('#eee');
|
||||
},
|
||||
opacity: function () {
|
||||
return 1;
|
||||
},
|
||||
...(options.point && options.point.attributes),
|
||||
};
|
||||
|
||||
this.fillVertexShader_ =
|
||||
(options.fill && options.fill.vertexShader) || FILL_VERTEX_SHADER;
|
||||
this.fillFragmentShader_ =
|
||||
(options.fill && options.fill.fragmentShader) || FILL_FRAGMENT_SHADER;
|
||||
this.fillAttributes_ = toAttributesArray(fillAttributes);
|
||||
|
||||
this.strokeVertexShader_ =
|
||||
(options.stroke && options.stroke.vertexShader) || STROKE_VERTEX_SHADER;
|
||||
this.strokeFragmentShader_ =
|
||||
(options.stroke && options.stroke.fragmentShader) ||
|
||||
STROKE_FRAGMENT_SHADER;
|
||||
this.strokeAttributes_ = toAttributesArray(strokeAttributes);
|
||||
|
||||
this.pointVertexShader_ =
|
||||
(options.point && options.point.vertexShader) || POINT_VERTEX_SHADER;
|
||||
this.pointFragmentShader_ =
|
||||
(options.point && options.point.fragmentShader) || POINT_FRAGMENT_SHADER;
|
||||
this.pointAttributes_ = toAttributesArray(pointAttributes);
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.worker_ = createWebGLWorker();
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.batch_ = new MixedGeometryBatch();
|
||||
|
||||
const source = this.getLayer().getSource();
|
||||
this.batch_.addFeatures(source.getFeatures());
|
||||
this.sourceListenKeys_ = [
|
||||
listen(
|
||||
source,
|
||||
VectorEventType.ADDFEATURE,
|
||||
this.handleSourceFeatureAdded_,
|
||||
this
|
||||
),
|
||||
listen(
|
||||
source,
|
||||
VectorEventType.CHANGEFEATURE,
|
||||
this.handleSourceFeatureChanged_,
|
||||
this
|
||||
),
|
||||
listen(
|
||||
source,
|
||||
VectorEventType.REMOVEFEATURE,
|
||||
this.handleSourceFeatureDelete_,
|
||||
this
|
||||
),
|
||||
listen(
|
||||
source,
|
||||
VectorEventType.CLEAR,
|
||||
this.handleSourceFeatureClear_,
|
||||
this
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
afterHelperCreated() {
|
||||
this.polygonRenderer_ = new PolygonBatchRenderer(
|
||||
this.helper,
|
||||
this.worker_,
|
||||
this.fillVertexShader_,
|
||||
this.fillFragmentShader_,
|
||||
this.fillAttributes_
|
||||
);
|
||||
this.pointRenderer_ = new PointBatchRenderer(
|
||||
this.helper,
|
||||
this.worker_,
|
||||
this.pointVertexShader_,
|
||||
this.pointFragmentShader_,
|
||||
this.pointAttributes_
|
||||
);
|
||||
this.lineStringRenderer_ = new LineStringBatchRenderer(
|
||||
this.helper,
|
||||
this.worker_,
|
||||
this.strokeVertexShader_,
|
||||
this.strokeFragmentShader_,
|
||||
this.strokeAttributes_
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
||||
* @private
|
||||
*/
|
||||
handleSourceFeatureAdded_(event) {
|
||||
const feature = event.feature;
|
||||
this.batch_.addFeature(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
||||
* @private
|
||||
*/
|
||||
handleSourceFeatureChanged_(event) {
|
||||
const feature = event.feature;
|
||||
this.batch_.changeFeature(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
||||
* @private
|
||||
*/
|
||||
handleSourceFeatureDelete_(event) {
|
||||
const feature = event.feature;
|
||||
this.batch_.removeFeature(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
handleSourceFeatureClear_() {
|
||||
this.batch_.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the layer.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
* @return {HTMLElement} The rendered element.
|
||||
*/
|
||||
renderFrame(frameState) {
|
||||
const gl = this.helper.getGL();
|
||||
this.preRender(gl, frameState);
|
||||
|
||||
const layer = this.getLayer();
|
||||
const vectorSource = layer.getSource();
|
||||
const projection = frameState.viewState.projection;
|
||||
const multiWorld = vectorSource.getWrapX() && projection.canWrapX();
|
||||
const projectionExtent = projection.getExtent();
|
||||
const extent = frameState.extent;
|
||||
const worldWidth = multiWorld ? getWidth(projectionExtent) : null;
|
||||
const endWorld = multiWorld
|
||||
? Math.ceil((extent[2] - projectionExtent[2]) / worldWidth) + 1
|
||||
: 1;
|
||||
let world = multiWorld
|
||||
? Math.floor((extent[0] - projectionExtent[0]) / worldWidth)
|
||||
: 0;
|
||||
|
||||
do {
|
||||
this.polygonRenderer_.render(
|
||||
this.batch_.polygonBatch,
|
||||
this.currentTransform_,
|
||||
frameState,
|
||||
world * worldWidth
|
||||
);
|
||||
this.lineStringRenderer_.render(
|
||||
this.batch_.lineStringBatch,
|
||||
this.currentTransform_,
|
||||
frameState,
|
||||
world * worldWidth
|
||||
);
|
||||
this.pointRenderer_.render(
|
||||
this.batch_.pointBatch,
|
||||
this.currentTransform_,
|
||||
frameState,
|
||||
world * worldWidth
|
||||
);
|
||||
} while (++world < endWorld);
|
||||
|
||||
this.helper.finalizeDraw(frameState);
|
||||
|
||||
const canvas = this.helper.getCanvas();
|
||||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||||
const opacity = layerState.opacity;
|
||||
if (opacity !== parseFloat(canvas.style.opacity)) {
|
||||
canvas.style.opacity = String(opacity);
|
||||
}
|
||||
|
||||
this.postRender(gl, frameState);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether renderFrame should be called.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
* @return {boolean} Layer is ready to be rendered.
|
||||
*/
|
||||
prepareFrameInternal(frameState) {
|
||||
const layer = this.getLayer();
|
||||
const vectorSource = layer.getSource();
|
||||
const viewState = frameState.viewState;
|
||||
const viewNotMoving =
|
||||
!frameState.viewHints[ViewHint.ANIMATING] &&
|
||||
!frameState.viewHints[ViewHint.INTERACTING];
|
||||
const extentChanged = !equals(this.previousExtent_, frameState.extent);
|
||||
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
|
||||
|
||||
if (sourceChanged) {
|
||||
this.sourceRevision_ = vectorSource.getRevision();
|
||||
}
|
||||
|
||||
if (viewNotMoving && (extentChanged || sourceChanged)) {
|
||||
const projection = viewState.projection;
|
||||
const resolution = viewState.resolution;
|
||||
|
||||
const renderBuffer =
|
||||
layer instanceof BaseVector ? layer.getRenderBuffer() : 0;
|
||||
const extent = buffer(frameState.extent, renderBuffer * resolution);
|
||||
vectorSource.loadFeatures(extent, resolution, projection);
|
||||
|
||||
this.ready = false;
|
||||
let remaining = 3;
|
||||
const rebuildCb = () => {
|
||||
remaining--;
|
||||
this.ready = remaining <= 0;
|
||||
this.getLayer().changed();
|
||||
};
|
||||
|
||||
this.polygonRenderer_.rebuild(
|
||||
this.batch_.polygonBatch,
|
||||
frameState,
|
||||
'Polygon',
|
||||
rebuildCb
|
||||
);
|
||||
this.lineStringRenderer_.rebuild(
|
||||
this.batch_.lineStringBatch,
|
||||
frameState,
|
||||
'LineString',
|
||||
rebuildCb
|
||||
);
|
||||
this.pointRenderer_.rebuild(
|
||||
this.batch_.pointBatch,
|
||||
frameState,
|
||||
'Point',
|
||||
rebuildCb
|
||||
);
|
||||
this.previousExtent_ = frameState.extent.slice();
|
||||
}
|
||||
|
||||
this.helper.makeProjectionTransform(frameState, this.currentTransform_);
|
||||
this.helper.prepareDraw(frameState);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
|
||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||
* @param {import("../vector.js").FeatureCallback<T>} callback Feature callback.
|
||||
* @param {Array<import("../Map.js").HitMatch<T>>} matches The hit detected matches with tolerance.
|
||||
* @return {T|undefined} Callback result.
|
||||
* @template T
|
||||
*/
|
||||
forEachFeatureAtCoordinate(
|
||||
coordinate,
|
||||
frameState,
|
||||
hitTolerance,
|
||||
callback,
|
||||
matches
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up.
|
||||
*/
|
||||
disposeInternal() {
|
||||
this.worker_.terminate();
|
||||
this.layer_ = null;
|
||||
this.sourceListenKeys_.forEach(function (key) {
|
||||
unlistenByKey(key);
|
||||
});
|
||||
this.sourceListenKeys_ = null;
|
||||
super.disposeInternal();
|
||||
}
|
||||
}
|
||||
|
||||
export default WebGLVectorLayerRenderer;
|
||||
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* @module ol/renderer/webgl/shaders
|
||||
*/
|
||||
import {asArray} from '../../color.js';
|
||||
|
||||
/** @typedef {'color'|'opacity'|'width'} DefaultAttributes */
|
||||
|
||||
/**
|
||||
* Packs red/green/blue channels of a color into a single float value; alpha is ignored.
|
||||
* This is how the color is expected to be computed.
|
||||
* @param {import("../../color.js").Color|string} color Color as array of numbers or string
|
||||
* @return {number} Float value containing the color
|
||||
*/
|
||||
export function packColor(color) {
|
||||
const array = asArray(color);
|
||||
const r = array[0] * 256 * 256;
|
||||
const g = array[1] * 256;
|
||||
const b = array[2];
|
||||
return r + g + b;
|
||||
}
|
||||
|
||||
const DECODE_COLOR_EXPRESSION = `vec3(
|
||||
fract(floor(a_color / 256.0 / 256.0) / 256.0),
|
||||
fract(floor(a_color / 256.0) / 256.0),
|
||||
fract(a_color / 256.0)
|
||||
);`;
|
||||
|
||||
/**
|
||||
* Default polygon vertex shader.
|
||||
* Relies on the color and opacity attributes.
|
||||
* @type {string}
|
||||
*/
|
||||
export const FILL_VERTEX_SHADER = `
|
||||
precision mediump float;
|
||||
uniform mat4 u_projectionMatrix;
|
||||
attribute vec2 a_position;
|
||||
attribute float a_color;
|
||||
attribute float a_opacity;
|
||||
varying vec3 v_color;
|
||||
varying float v_opacity;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0);
|
||||
v_color = ${DECODE_COLOR_EXPRESSION}
|
||||
v_opacity = a_opacity;
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Default polygon fragment shader.
|
||||
* @type {string}
|
||||
*/
|
||||
export const FILL_FRAGMENT_SHADER = `
|
||||
precision mediump float;
|
||||
varying vec3 v_color;
|
||||
varying float v_opacity;
|
||||
|
||||
void main(void) {
|
||||
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Default linestring vertex shader.
|
||||
* Relies on color, opacity and width attributes.
|
||||
* @type {string}
|
||||
*/
|
||||
export const STROKE_VERTEX_SHADER = `
|
||||
precision mediump float;
|
||||
uniform mat4 u_projectionMatrix;
|
||||
uniform vec2 u_sizePx;
|
||||
attribute vec2 a_segmentStart;
|
||||
attribute vec2 a_segmentEnd;
|
||||
attribute float a_parameters;
|
||||
attribute float a_color;
|
||||
attribute float a_opacity;
|
||||
attribute float a_width;
|
||||
varying vec2 v_segmentStart;
|
||||
varying vec2 v_segmentEnd;
|
||||
varying float v_angleStart;
|
||||
varying float v_angleEnd;
|
||||
varying vec3 v_color;
|
||||
varying float v_opacity;
|
||||
varying float v_width;
|
||||
|
||||
vec2 worldToPx(vec2 worldPos) {
|
||||
vec4 screenPos = u_projectionMatrix * vec4(worldPos, 0.0, 1.0);
|
||||
return (0.5 * screenPos.xy + 0.5) * u_sizePx;
|
||||
}
|
||||
|
||||
vec4 pxToScreen(vec2 pxPos) {
|
||||
vec2 screenPos = pxPos * 4.0 / u_sizePx;
|
||||
return vec4(screenPos.xy, 0.0, 0.0);
|
||||
}
|
||||
|
||||
vec2 getOffsetDirection(vec2 normalPx, vec2 tangentPx, float joinAngle) {
|
||||
if (cos(joinAngle) > 0.93) return normalPx - tangentPx;
|
||||
float halfAngle = joinAngle / 2.0;
|
||||
vec2 angleBisectorNormal = vec2(
|
||||
sin(halfAngle) * normalPx.x + cos(halfAngle) * normalPx.y,
|
||||
-cos(halfAngle) * normalPx.x + sin(halfAngle) * normalPx.y
|
||||
);
|
||||
float length = 1.0 / sin(halfAngle);
|
||||
return angleBisectorNormal * length;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
float anglePrecision = 1500.0;
|
||||
float paramShift = 10000.0;
|
||||
v_angleStart = fract(a_parameters / paramShift) * paramShift / anglePrecision;
|
||||
v_angleEnd = fract(floor(a_parameters / paramShift + 0.5) / paramShift) * paramShift / anglePrecision;
|
||||
float vertexNumber = floor(a_parameters / paramShift / paramShift + 0.0001);
|
||||
vec2 tangentPx = worldToPx(a_segmentEnd) - worldToPx(a_segmentStart);
|
||||
tangentPx = normalize(tangentPx);
|
||||
vec2 normalPx = vec2(-tangentPx.y, tangentPx.x);
|
||||
float normalDir = vertexNumber < 0.5 || (vertexNumber > 1.5 && vertexNumber < 2.5) ? 1.0 : -1.0;
|
||||
float tangentDir = vertexNumber < 1.5 ? 1.0 : -1.0;
|
||||
float angle = vertexNumber < 1.5 ? v_angleStart : v_angleEnd;
|
||||
vec2 offsetPx = getOffsetDirection(normalPx * normalDir, tangentDir * tangentPx, angle) * a_width * 0.5;
|
||||
vec2 position = vertexNumber < 1.5 ? a_segmentStart : a_segmentEnd;
|
||||
gl_Position = u_projectionMatrix * vec4(position, 0.0, 1.0) + pxToScreen(offsetPx);
|
||||
v_segmentStart = worldToPx(a_segmentStart);
|
||||
v_segmentEnd = worldToPx(a_segmentEnd);
|
||||
v_color = ${DECODE_COLOR_EXPRESSION}
|
||||
v_opacity = a_opacity;
|
||||
v_width = a_width;
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Default linestring fragment shader.
|
||||
* @type {string}
|
||||
*/
|
||||
export const STROKE_FRAGMENT_SHADER = `
|
||||
precision mediump float;
|
||||
uniform float u_pixelRatio;
|
||||
varying vec2 v_segmentStart;
|
||||
varying vec2 v_segmentEnd;
|
||||
varying float v_angleStart;
|
||||
varying float v_angleEnd;
|
||||
varying vec3 v_color;
|
||||
varying float v_opacity;
|
||||
varying float v_width;
|
||||
|
||||
float segmentDistanceField(vec2 point, vec2 start, vec2 end, float radius) {
|
||||
vec2 startToPoint = point - start;
|
||||
vec2 startToEnd = end - start;
|
||||
float ratio = clamp(dot(startToPoint, startToEnd) / dot(startToEnd, startToEnd), 0.0, 1.0);
|
||||
float dist = length(startToPoint - ratio * startToEnd);
|
||||
return 1.0 - smoothstep(radius - 1.0, radius, dist);
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
vec2 v_currentPoint = gl_FragCoord.xy / u_pixelRatio;
|
||||
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
|
||||
gl_FragColor *= segmentDistanceField(v_currentPoint, v_segmentStart, v_segmentEnd, v_width);
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Default point vertex shader.
|
||||
* Relies on color and opacity attributes.
|
||||
* @type {string}
|
||||
*/
|
||||
export const POINT_VERTEX_SHADER = `
|
||||
precision mediump float;
|
||||
uniform mat4 u_projectionMatrix;
|
||||
uniform mat4 u_offsetScaleMatrix;
|
||||
attribute vec2 a_position;
|
||||
attribute float a_index;
|
||||
attribute float a_color;
|
||||
attribute float a_opacity;
|
||||
varying vec2 v_texCoord;
|
||||
varying vec3 v_color;
|
||||
varying float v_opacity;
|
||||
|
||||
void main(void) {
|
||||
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||
float size = 6.0;
|
||||
float offsetX = a_index == 0.0 || a_index == 3.0 ? -size / 2.0 : size / 2.0;
|
||||
float offsetY = a_index == 0.0 || a_index == 1.0 ? -size / 2.0 : size / 2.0;
|
||||
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
|
||||
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
||||
float u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||
float v = a_index == 0.0 || a_index == 1.0 ? 0.0 : 1.0;
|
||||
v_texCoord = vec2(u, v);
|
||||
v_color = ${DECODE_COLOR_EXPRESSION}
|
||||
v_opacity = a_opacity;
|
||||
}`;
|
||||
|
||||
/**
|
||||
* Default point fragment shader.
|
||||
* @type {string}
|
||||
*/
|
||||
export const POINT_FRAGMENT_SHADER = `
|
||||
precision mediump float;
|
||||
varying vec3 v_color;
|
||||
varying float v_opacity;
|
||||
|
||||
void main(void) {
|
||||
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
|
||||
}`;
|
||||
Reference in New Issue
Block a user