Include WebGL context in render events for WebGL layers
This commit is contained in:
16
examples/webgl-layer-swipe.html
Normal file
16
examples/webgl-layer-swipe.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: Layer Swipe (WebGL)
|
||||||
|
shortdesc: Cropping a WebGL tile layer
|
||||||
|
docs: >
|
||||||
|
The <code>prerender</code> and <code>postrender</code> events on a WebGL tile layer can be
|
||||||
|
used to manipulate the WebGL context before and after rendering. In this case, the
|
||||||
|
<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/scissor"><code>gl.scissor()</code></a>
|
||||||
|
method is called to clip the top layer based on the position of a slider.
|
||||||
|
tags: "swipe, webgl"
|
||||||
|
cloak:
|
||||||
|
- key: get_your_own_D6rA4zTHduk6KOKTXzGB
|
||||||
|
value: Get your own API key at https://www.maptiler.com/cloud/
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
<input id="swipe" type="range" style="width: 100%">
|
||||||
61
examples/webgl-layer-swipe.js
Normal file
61
examples/webgl-layer-swipe.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import OSM from '../src/ol/source/OSM.js';
|
||||||
|
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import XYZ from '../src/ol/source/XYZ.js';
|
||||||
|
import {getRenderPixel} from '../src/ol/render.js';
|
||||||
|
|
||||||
|
const osm = new TileLayer({
|
||||||
|
source: new OSM({wrapX: true}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
|
||||||
|
|
||||||
|
const imagery = new TileLayer({
|
||||||
|
source: new XYZ({
|
||||||
|
url: 'https://api.maptiler.com/tiles/satellite/{z}/{x}/{y}.jpg?key=' + key,
|
||||||
|
attributions:
|
||||||
|
'<a href="https://www.maptiler.com/copyright/" target="_blank">© MapTiler</a> ' +
|
||||||
|
'<a href="https://www.openstreetmap.org/copyright" target="_blank">© OpenStreetMap contributors</a>',
|
||||||
|
crossOrigin: '',
|
||||||
|
maxZoom: 20,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [osm, imagery],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 2,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const swipe = document.getElementById('swipe');
|
||||||
|
|
||||||
|
imagery.on('prerender', function (event) {
|
||||||
|
const gl = event.context;
|
||||||
|
gl.enable(gl.SCISSOR_TEST);
|
||||||
|
|
||||||
|
const mapSize = map.getSize(); // [width, height] in CSS pixels
|
||||||
|
|
||||||
|
// get render coordinates and dimensions given CSS coordinates
|
||||||
|
const bottomLeft = getRenderPixel(event, [0, mapSize[1]]);
|
||||||
|
const topRight = getRenderPixel(event, [mapSize[0], 0]);
|
||||||
|
|
||||||
|
const width = Math.round((topRight[0] - bottomLeft[0]) * (swipe.value / 100));
|
||||||
|
const height = topRight[1] - bottomLeft[1];
|
||||||
|
|
||||||
|
gl.scissor(bottomLeft[0], bottomLeft[1], width, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
imagery.on('postrender', function (event) {
|
||||||
|
const gl = event.context;
|
||||||
|
gl.disable(gl.SCISSOR_TEST);
|
||||||
|
});
|
||||||
|
|
||||||
|
const listener = function () {
|
||||||
|
map.render();
|
||||||
|
};
|
||||||
|
swipe.addEventListener('input', listener);
|
||||||
|
swipe.addEventListener('change', listener);
|
||||||
@@ -88,6 +88,10 @@ export function toContext(context, opt_options) {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
export function getVectorContext(event) {
|
export function getVectorContext(event) {
|
||||||
|
if (!(event.context instanceof CanvasRenderingContext2D)) {
|
||||||
|
throw new Error('Only works for render events from Canvas 2D layers');
|
||||||
|
}
|
||||||
|
|
||||||
// canvas may be at a different pixel ratio than frameState.pixelRatio
|
// canvas may be at a different pixel ratio than frameState.pixelRatio
|
||||||
const canvasPixelRatio = event.inversePixelTransform[0];
|
const canvasPixelRatio = event.inversePixelTransform[0];
|
||||||
const frameState = event.frameState;
|
const frameState = event.frameState;
|
||||||
@@ -107,6 +111,7 @@ export function getVectorContext(event) {
|
|||||||
frameState.viewState.projection
|
frameState.viewState.projection
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CanvasImmediateRenderer(
|
return new CanvasImmediateRenderer(
|
||||||
event.context,
|
event.context,
|
||||||
canvasPixelRatio,
|
canvasPixelRatio,
|
||||||
@@ -127,7 +132,5 @@ export function getVectorContext(event) {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
export function getRenderPixel(event, pixel) {
|
export function getRenderPixel(event, pixel) {
|
||||||
const result = pixel.slice(0);
|
return applyTransform(event.inversePixelTransform, pixel.slice(0));
|
||||||
applyTransform(event.inversePixelTransform.slice(), result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class RenderEvent extends Event {
|
|||||||
* @param {import("../transform.js").Transform} [opt_inversePixelTransform] Transform for
|
* @param {import("../transform.js").Transform} [opt_inversePixelTransform] Transform for
|
||||||
* CSS pixels to rendered pixels.
|
* CSS pixels to rendered pixels.
|
||||||
* @param {import("../PluggableMap.js").FrameState} [opt_frameState] Frame state.
|
* @param {import("../PluggableMap.js").FrameState} [opt_frameState] Frame state.
|
||||||
* @param {?CanvasRenderingContext2D} [opt_context] Context.
|
* @param {?(CanvasRenderingContext2D|WebGLRenderingContext)} [opt_context] Context.
|
||||||
*/
|
*/
|
||||||
constructor(type, opt_inversePixelTransform, opt_frameState, opt_context) {
|
constructor(type, opt_inversePixelTransform, opt_frameState, opt_context) {
|
||||||
super(type);
|
super(type);
|
||||||
@@ -31,9 +31,10 @@ class RenderEvent extends Event {
|
|||||||
this.frameState = opt_frameState;
|
this.frameState = opt_frameState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Canvas context. Not available when the event is dispatched by the map. Only available
|
* Canvas context. Not available when the event is dispatched by the map. For Canvas 2D layers,
|
||||||
* when a Canvas renderer is used, null otherwise.
|
* the context will be the 2D rendering context. For WebGL layers, the context will be the WebGL
|
||||||
* @type {CanvasRenderingContext2D|null|undefined}
|
* context.
|
||||||
|
* @type {CanvasRenderingContext2D|WebGLRenderingContext|undefined}
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
this.context = opt_context;
|
this.context = opt_context;
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import LayerRenderer from '../Layer.js';
|
|||||||
import RenderEvent from '../../render/Event.js';
|
import RenderEvent from '../../render/Event.js';
|
||||||
import RenderEventType from '../../render/EventType.js';
|
import RenderEventType from '../../render/EventType.js';
|
||||||
import WebGLHelper from '../../webgl/Helper.js';
|
import WebGLHelper from '../../webgl/Helper.js';
|
||||||
|
import {
|
||||||
|
compose as composeTransform,
|
||||||
|
create as createTransform,
|
||||||
|
} from '../../transform.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
@@ -58,6 +62,14 @@ class WebGLLayerRenderer extends LayerRenderer {
|
|||||||
|
|
||||||
const options = opt_options || {};
|
const options = opt_options || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transform for viewport CSS pixels to rendered pixels. This transform is only
|
||||||
|
* set before dispatching rendering events.
|
||||||
|
* @private
|
||||||
|
* @type {import("../../transform.js").Transform}
|
||||||
|
*/
|
||||||
|
this.inversePixelTransform_ = createTransform();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {WebGLHelper}
|
* @type {WebGLHelper}
|
||||||
* @protected
|
* @protected
|
||||||
@@ -84,32 +96,50 @@ class WebGLLayerRenderer extends LayerRenderer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../../render/EventType.js").default} type Event type.
|
* @param {import("../../render/EventType.js").default} type Event type.
|
||||||
|
* @param {WebGLRenderingContext} context The rendering context.
|
||||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
dispatchRenderEvent_(type, frameState) {
|
dispatchRenderEvent_(type, context, frameState) {
|
||||||
const layer = this.getLayer();
|
const layer = this.getLayer();
|
||||||
if (layer.hasListener(type)) {
|
if (layer.hasListener(type)) {
|
||||||
// RenderEvent does not get a context or an inversePixelTransform, because WebGL allows much less direct editing than Canvas2d does.
|
composeTransform(
|
||||||
const event = new RenderEvent(type, null, frameState, null);
|
this.inversePixelTransform_,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
frameState.pixelRatio,
|
||||||
|
-frameState.pixelRatio,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
-frameState.size[1]
|
||||||
|
);
|
||||||
|
|
||||||
|
const event = new RenderEvent(
|
||||||
|
type,
|
||||||
|
this.inversePixelTransform_,
|
||||||
|
frameState,
|
||||||
|
context
|
||||||
|
);
|
||||||
layer.dispatchEvent(event);
|
layer.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {WebGLRenderingContext} context The rendering context.
|
||||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
preRender(frameState) {
|
preRender(context, frameState) {
|
||||||
this.dispatchRenderEvent_(RenderEventType.PRERENDER, frameState);
|
this.dispatchRenderEvent_(RenderEventType.PRERENDER, context, frameState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {WebGLRenderingContext} context The rendering context.
|
||||||
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
postRender(frameState) {
|
postRender(context, frameState) {
|
||||||
this.dispatchRenderEvent_(RenderEventType.POSTRENDER, frameState);
|
this.dispatchRenderEvent_(RenderEventType.POSTRENDER, context, frameState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -411,7 +411,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
* @return {HTMLElement} The rendered element.
|
* @return {HTMLElement} The rendered element.
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState) {
|
renderFrame(frameState) {
|
||||||
this.preRender(frameState);
|
const gl = this.helper.getGL();
|
||||||
|
this.preRender(gl, frameState);
|
||||||
|
|
||||||
const renderCount = this.indicesBuffer_.getSize();
|
const renderCount = this.indicesBuffer_.getSize();
|
||||||
this.helper.drawElements(0, renderCount);
|
this.helper.drawElements(0, renderCount);
|
||||||
@@ -429,7 +430,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.hitRenderTarget_.clearCachedData();
|
this.hitRenderTarget_.clearCachedData();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.postRender(frameState);
|
this.postRender(gl, frameState);
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,7 +292,8 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
* @return {HTMLElement} The rendered element.
|
* @return {HTMLElement} The rendered element.
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState) {
|
renderFrame(frameState) {
|
||||||
this.preRender(frameState);
|
const gl = this.helper.getGL();
|
||||||
|
this.preRender(gl, frameState);
|
||||||
|
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||||||
@@ -386,8 +387,6 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
.map(Number)
|
.map(Number)
|
||||||
.sort(numberSafeCompareFunction);
|
.sort(numberSafeCompareFunction);
|
||||||
|
|
||||||
const gl = this.helper.getGL();
|
|
||||||
|
|
||||||
const centerX = viewState.center[0];
|
const centerX = viewState.center[0];
|
||||||
const centerY = viewState.center[1];
|
const centerY = viewState.center[1];
|
||||||
|
|
||||||
@@ -509,7 +508,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
|
|
||||||
frameState.postRenderFunctions.push(postRenderFunction);
|
frameState.postRenderFunctions.push(postRenderFunction);
|
||||||
|
|
||||||
this.postRender(frameState);
|
this.postRender(gl, frameState);
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import View from '../../../../../src/ol/View.js';
|
|||||||
import WebGLHelper from '../../../../../src/ol/webgl/Helper.js';
|
import WebGLHelper from '../../../../../src/ol/webgl/Helper.js';
|
||||||
import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.js';
|
import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.js';
|
||||||
import {createCanvasContext2D} from '../../../../../src/ol/dom.js';
|
import {createCanvasContext2D} from '../../../../../src/ol/dom.js';
|
||||||
|
import {getRenderPixel} from '../../../../../src/ol/render.js';
|
||||||
|
|
||||||
describe('ol/layer/WebGLTile', function () {
|
describe('ol/layer/WebGLTile', function () {
|
||||||
/** @type {WebGLTileLayer} */
|
/** @type {WebGLTileLayer} */
|
||||||
@@ -129,6 +130,46 @@ describe('ol/layer/WebGLTile', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('dispatches a prerender event with WebGL context and inverse pixel transform', (done) => {
|
||||||
|
let called = false;
|
||||||
|
layer.on('prerender', (event) => {
|
||||||
|
expect(event.context).to.be.a(WebGLRenderingContext);
|
||||||
|
const mapSize = event.frameState.size;
|
||||||
|
const bottomLeft = getRenderPixel(event, [0, mapSize[1]]);
|
||||||
|
expect(bottomLeft).to.eql([0, 0]);
|
||||||
|
called = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
map.once('rendercomplete', () => {
|
||||||
|
expect(called).to.be(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
map.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches a postrender event with WebGL context and inverse pixel transform', (done) => {
|
||||||
|
let called = false;
|
||||||
|
layer.on('postrender', (event) => {
|
||||||
|
expect(event.context).to.be.a(WebGLRenderingContext);
|
||||||
|
const mapSize = event.frameState.size;
|
||||||
|
const topRight = getRenderPixel(event, [mapSize[1], 0]);
|
||||||
|
const pixelRatio = event.frameState.pixelRatio;
|
||||||
|
expect(topRight).to.eql([
|
||||||
|
mapSize[0] * pixelRatio,
|
||||||
|
mapSize[1] * pixelRatio,
|
||||||
|
]);
|
||||||
|
called = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
map.once('rendercomplete', () => {
|
||||||
|
expect(called).to.be(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
map.render();
|
||||||
|
});
|
||||||
|
|
||||||
it('tries to expire the source tile cache', (done) => {
|
it('tries to expire the source tile cache', (done) => {
|
||||||
const source = layer.getSource();
|
const source = layer.getSource();
|
||||||
const expire = sinon.spy(source, 'expireCache');
|
const expire = sinon.spy(source, 'expireCache');
|
||||||
|
|||||||
Reference in New Issue
Block a user