From d1ac4a3a0d216aab401f59cb150892e07f309c1b Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 28 Nov 2018 10:14:26 -0500 Subject: [PATCH] Autoshare webgl renderers. Autosharing is now the default. Renderers can be manually shared, and webgl layers can be manually be moved to different renderer. To manually move a layer to a different renderer, for instance, given two layers: `var layer1 = map.createLayer('feature', {renderer: 'webgl', autoshareRenderer: false}); var layer2 = map.createLayer('feature', {renderer: layer1.renderer(), autoshareRenderer: false});`, the second layer can be moved to a new renderer by doing: `layer2.switchRenderer(geo.createRenderer('webgl', layer2), true);`. --- CHANGELOG.md | 3 + src/canvas/quadFeature.js | 2 +- src/feature.js | 13 ++- src/layer.js | 18 +++ src/map.js | 4 +- src/webgl/contourFeature.js | 9 ++ src/webgl/layer.js | 140 ++++++++++++++++++++++ src/webgl/lineFeature.js | 9 +- src/webgl/pointFeature.js | 4 +- src/webgl/polygonFeature.js | 9 ++ src/webgl/quadFeature.js | 51 +++++++-- src/webgl/tileLayer.js | 20 +++- tests/cases/contourFeature.js | 4 + tests/cases/feature.js | 2 +- tests/cases/layer.js | 191 +++++++++++++++++++++++++++++++ tests/cases/polygonFeature.js | 10 ++ tests/cases/quadFeature.js | 3 + tests/data/weather.png | Bin 0 -> 6448 bytes tests/gl-cases/layerReorder.js | 4 +- tests/gl-cases/pixelAlignment.js | 2 +- tests/gl-cases/svgLines.js | 2 +- tests/gl-cases/ui.js | 2 +- tests/gl-cases/webglContour.js | 6 +- tests/gl-cases/webglLines.js | 4 +- 24 files changed, 472 insertions(+), 40 deletions(-) create mode 100644 tests/data/weather.png diff --git a/CHANGELOG.md b/CHANGELOG.md index e6c20292de..489ce6c8a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +### Features +- Layers that use webgl renderers automatically share contexts when possible. Layers can switch renderers manually as well. This largely avoids the limitation of number of webgl contexts in a browser. + ### Changes - The point clustering radius value is now in display pixels (#983) diff --git a/src/canvas/quadFeature.js b/src/canvas/quadFeature.js index 62e497dd41..2bdefdf126 100644 --- a/src/canvas/quadFeature.js +++ b/src/canvas/quadFeature.js @@ -81,7 +81,7 @@ var canvas_quadFeature = function (arg) { } } }); - if (render) { + if (render && m_this.renderer()) { m_this.renderer()._render(); } if (changing) { diff --git a/src/feature.js b/src/feature.js index f3113143d3..6d6ee6828c 100644 --- a/src/feature.js +++ b/src/feature.js @@ -33,8 +33,9 @@ var geo_event = require('./event'); * bin number, their order relative to one another is indeterminate and may * be unstable. A value of `null` will use the current position of the * feature within its parent's list of children as the bin number. - * @property {geo.renderer?} [renderer] A reference to the renderer used for - * the feature. + * @property {geo.renderer} [renderer] A reference to the renderer used for + * the feature. If `null` or unset or identical to `layer.renderer()`, the + * layer's renderer is used. * @property {geo.feature.styleSpec} [style] An object that contains style * values for the feature. */ @@ -120,7 +121,7 @@ var feature = function (arg) { m_gcs = arg.gcs, m_visible = arg.visible === undefined ? true : arg.visible, m_bin = arg.bin === undefined ? null : arg.bin, - m_renderer = arg.renderer === undefined ? null : arg.renderer, + m_renderer = arg.renderer === undefined || (m_layer && arg.renderer === m_layer.renderer()) ? null : arg.renderer, m_dataTime = timestamp(), m_buildTime = timestamp(), m_updateTime = timestamp(), @@ -574,7 +575,7 @@ var feature = function (arg) { * @returns {geo.renderer} The renderer used to render the feature. */ this.renderer = function () { - return m_renderer; + return m_renderer || (m_layer && m_layer.renderer()); }; /** @@ -610,8 +611,8 @@ var feature = function (arg) { var map = m_layer.map(); c = map.gcsToWorld(c, m_this.gcs()); c = map.worldToDisplay(c); - if (m_renderer.baseToLocal) { - c = m_renderer.baseToLocal(c); + if (m_this.renderer().baseToLocal) { + c = m_this.renderer().baseToLocal(c); } return c; }; diff --git a/src/layer.js b/src/layer.js index c003556d88..907a68edce 100644 --- a/src/layer.js +++ b/src/layer.js @@ -17,6 +17,14 @@ var rendererForAnnotations = require('./registry').rendererForAnnotations; * to determine the renderer. If a {@link geo.renderer} instance, the * renderer is not recreated; not all renderers can be shared by multiple * layers. + * @property {boolean} [autoshareRenderer=true] If truthy and the renderer + * supports it, auto-share renderers between layers. Currently, auto-sharing + * can only occur for webgl renderers, and will only occur between adjacent + * layers than have the same opacity. Shared renderers has slightly + * different behavior than non-shared renderers: changing z-index may result + * in rerendering and be slightly slower; only one DOM canvas is used for all + * shared renderers. Some features have slight z-stacking differences in + * shared versus non-shared renderers. * @property {HTMLElement} [canvas] If specified, use this canvas rather than * a canvas associaied with the renderer directly. Renderers may not support * sharing a canvas. @@ -87,6 +95,7 @@ var layer = function (arg) { arg.renderer instanceof renderer ? arg.renderer.api() : arg.renderer) : ( arg.annotations ? rendererForAnnotations(arg.annotations) : rendererForFeatures(arg.features)), + m_autoshareRenderer = arg.autoshareRenderer === undefined ? true : arg.autoshareRenderer, m_dataTime = timestamp(), m_updateTime = timestamp(), m_sticky = arg.sticky === undefined ? true : arg.sticky, @@ -125,6 +134,15 @@ var layer = function (arg) { return m_rendererName; }; + /** + * Get the setting of autoshareRenderer. + * + * @returns {boolean} + */ + this.autoshareRenderer = function () { + return m_autoshareRenderer; + }; + /** * Get or set the z-index of the layer. The z-index controls the display * order of the layers in much the same way as the CSS z-index property. diff --git a/src/map.js b/src/map.js index d70791296b..05174b496f 100644 --- a/src/map.js +++ b/src/map.js @@ -1761,13 +1761,13 @@ var map = function (arg) { layer.node().children('canvas').each(function () { var canvasElem = $(this); defer = defer.then(function () { - if (layer.renderer().api() === 'webgl') { + if (layer.renderer() && layer.renderer().api() === 'webgl') { layer.renderer()._renderFrame(); } drawLayerImageToContext(context, opacity, canvasElem, canvasElem[0]); }); }); - if (layer.node().children().not('canvas').length || !layer.node().children().length) { + if ((layer.node().children().not('canvas').length || !layer.node().children().length) && (!layer.renderer() || layer.renderer().api() !== 'webgl')) { defer = defer.then(function () { return util.htmlToImage(layer.node(), 1).done(function (img) { drawLayerImageToContext(context, 1, $([]), img); diff --git a/src/webgl/contourFeature.js b/src/webgl/contourFeature.js index 46525ce773..d058ebd659 100644 --- a/src/webgl/contourFeature.js +++ b/src/webgl/contourFeature.js @@ -192,6 +192,15 @@ var webgl_contourFeature = function (arg) { m_mapper.setGeometryData(geom); }; + /** + * List vgl actors. + * + * @returns {vgl.actor[]} The list of actors. + */ + this.actors = function () { + return m_actor ? [m_actor] : []; + }; + /** * Build. */ diff --git a/src/webgl/layer.js b/src/webgl/layer.js index 569937a0e4..80faf8789e 100644 --- a/src/webgl/layer.js +++ b/src/webgl/layer.js @@ -3,10 +3,31 @@ var registerLayerAdjustment = require('../registry').registerLayerAdjustment; var webgl_layer = function () { 'use strict'; + var createRenderer = require('../registry').createRenderer; + var geo_event = require('../event'); + var webglRenderer = require('./webglRenderer'); + var m_this = this, + s_init = this._init, + s_opacity = this.opacity, s_visible = this.visible, s_zIndex = this.zIndex; + /** + * Get or set the current layer opacity. The opacity is in the range [0-1]. + * + * @param {number} [opacity] If specified, set the opacity. Otherwise, + * return the opacity. + * @returns {number|this} The current opacity or the current layer. + */ + this.opacity = function (opacity) { + var result = s_opacity.apply(m_this, arguments); + if (opacity !== undefined && m_this.initialized()) { + m_this.map()._updateAutoshareRenderers(); + } + return result; + }; + /** * Get/Set visibility of the layer. * @@ -45,9 +66,128 @@ var webgl_layer = function () { /* If the z-index has changed, schedule rerendering the layer. */ m_this.map().scheduleAnimationFrame(m_this._update, true); m_this.renderer()._render(); + m_this.map()._updateAutoshareRenderers(); } return result; }; + + /** + * Move all of the objects associated with this layer to a different webgl + * renderer. This runs the _cleanup routine for any feature or child of the + * layer (if it has one), removes all actors associated with this layer from + * the existing renderer, then adds those actors to the new renderer and + * calls `_update` on any feature that had a `_cleanup` routine. If desired, + * the old and new renderers are both asked to rerender. If moving multiple + * renderers for multiple layers, rerendering can be delayed. + * + * @param {geo.webgl.webglRenderer} newRenderer The renderer to move to. + * @param {boolean} [rerender=false] If truthy, rerender after the switch. + * @returns {this} + */ + this.switchRenderer = function (newRenderer, rerender) { + if (newRenderer instanceof webglRenderer && newRenderer !== m_this.renderer()) { + var oldRenderer = m_this.renderer(), + actors = [], + updates = []; + m_this.map().listSceneObjects([m_this]).forEach(function (obj) { + if (obj._cleanup) { + obj._cleanup(); + if (obj._update) { + updates.push(obj); + } + } + if (obj.actors) { + actors = actors.concat(obj.actors()); + } + }); + actors.forEach(function (actor) { + oldRenderer.contextRenderer().removeActor(actor); + newRenderer.contextRenderer().addActor(actor); + }); + m_this._renderer(newRenderer); + m_this._canvas(newRenderer.canvas()); + if (rerender && (actors.length || updates.length)) { + oldRenderer._render(); + updates.forEach(function (obj) { + obj._update(); + }); + newRenderer._render(); + } + } + return m_this; + }; + + /** + * Initialize after the layer is added to the map. + * + * @returns {this} + */ + this._init = function () { + + var map = m_this.map(); + if (!map._updateAutoshareRenderers) { + /** + * Update all webgl autoshareRenderer layers so that appropriate groups + * of layers share renderers. Each group must (a) be continguous in + * z-space (not separated by a non-autoshare layer or a non-webgl layer), + * and (b) have the same opacity. The lowest layer in each group will + * contain the actual canvas and context. This rerenders as needed. + */ + map._updateAutoshareRenderers = function () { + var layers = map.sortedLayers(), + renderer, + used_canvases = [], + canvases = [], + rerender_list = [], + opacity; + layers.forEach(function (layer) { + if (!layer.autoshareRenderer() || !layer.renderer() || layer.renderer().api() !== webglRenderer.apiname) { + renderer = null; + } else if (!renderer || layer.opacity() !== opacity) { + if (!layer.node()[0].contains(layer.renderer().canvas()[0])) { + layer.switchRenderer(createRenderer(webglRenderer.apiname, layer), false); + rerender_list.push(layer.renderer()); + } + renderer = layer.renderer(); + used_canvases.push(renderer.canvas()[0]); + opacity = layer.opacity(); + } else { + if (layer.renderer() !== renderer) { + rerender_list.push(layer.renderer()); + canvases.push(layer.renderer().canvas()[0]); + layer.switchRenderer(renderer, false); + rerender_list.push(layer.renderer()); + } + } + }); + layers.forEach(function (layer) { + if (rerender_list.indexOf(layer.renderer()) >= 0) { + if (layer._update) { + layer._update(); + } + } + }); + layers.forEach(function (layer) { + if (rerender_list.indexOf(layer.renderer()) >= 0) { + layer.renderer()._render(); + rerender_list = rerender_list.filter((val) => val !== layer.renderer()); + } + }); + canvases.forEach(function (canvas) { + if (used_canvases.indexOf(canvas) < 0) { + canvas.remove(); + used_canvases.push(canvas); + } + }); + }; + + map.geoOn(geo_event.layerAdd, () => map._updateAutoshareRenderers()); + map.geoOn(geo_event.layerRemove, () => map._updateAutoshareRenderers()); + } + + return s_init.apply(m_this, arguments); + }; + }; registerLayerAdjustment('webgl', 'all', webgl_layer); diff --git a/src/webgl/lineFeature.js b/src/webgl/lineFeature.js index 07fd314a5d..241fe2271b 100644 --- a/src/webgl/lineFeature.js +++ b/src/webgl/lineFeature.js @@ -461,15 +461,12 @@ var webgl_lineFeature = function (arg) { }; /** - * Return list of vgl actors used for rendering. + * List vgl actors. * - * @returns {vgl.actor[]} + * @returns {vgl.actor[]} The list of actors. */ this.actors = function () { - if (!m_actor) { - return []; - } - return [m_actor]; + return m_actor ? [m_actor] : []; }; /** diff --git a/src/webgl/pointFeature.js b/src/webgl/pointFeature.js index 1a5b6fdfea..a9c8396646 100644 --- a/src/webgl/pointFeature.js +++ b/src/webgl/pointFeature.js @@ -229,9 +229,9 @@ var webgl_pointFeature = function (arg) { } /** - * Return list of vgl actors used for rendering. + * List vgl actors. * - * @returns {vgl.actor[]} + * @returns {vgl.actor[]} The list of actors. */ this.actors = function () { if (!m_actor) { diff --git a/src/webgl/polygonFeature.js b/src/webgl/polygonFeature.js index 4a73d77642..464fed3b46 100644 --- a/src/webgl/polygonFeature.js +++ b/src/webgl/polygonFeature.js @@ -298,6 +298,15 @@ var webgl_polygonFeature = function (arg) { s_init.call(m_this, arg); }; + /** + * List vgl actors. + * + * @returns {vgl.actor[]} The list of actors. + */ + this.actors = function () { + return [m_actor]; + }; + /** * Build. * diff --git a/src/webgl/quadFeature.js b/src/webgl/quadFeature.js index 274c295c9d..b080fcd53d 100644 --- a/src/webgl/quadFeature.js +++ b/src/webgl/quadFeature.js @@ -31,7 +31,6 @@ var webgl_quadFeature = function (arg) { var m_this = this, s_exit = this._exit, - s_init = this._init, s_update = this._update, m_modelViewUniform, m_actor_image, m_actor_color, m_glBuffers = {}, m_imgposbuf, @@ -139,6 +138,22 @@ var webgl_quadFeature = function (arg) { return mapper; } + /** + * List vgl actors. + * + * @returns {vgl.actor[]} The list of actors. + */ + this.actors = function () { + var actors = []; + if (m_actor_image) { + actors.push(m_actor_image); + } + if (m_actor_color) { + actors.push(m_actor_color); + } + return actors; + }; + /** * Build this feature. */ @@ -241,7 +256,8 @@ var webgl_quadFeature = function (arg) { /** * Check all of the image quads. If any do not have the correct texture, - * update them. */ + * update them. + */ this._updateTextures = function () { var texture; @@ -390,16 +406,9 @@ var webgl_quadFeature = function (arg) { }; /** - * Initialize. + * Cleanup. */ - this._init = function () { - s_init.call(m_this, arg); - }; - - /** - * Destroy. - */ - this._exit = function () { + this._cleanup = function () { if (m_actor_image) { m_this.renderer().contextRenderer().removeActor(m_actor_image); m_actor_image = null; @@ -408,6 +417,26 @@ var webgl_quadFeature = function (arg) { m_this.renderer().contextRenderer().removeActor(m_actor_color); m_actor_color = null; } + m_imgposbuf = undefined; + m_clrposbuf = undefined; + Object.keys(m_glBuffers).forEach(function (key) { delete m_glBuffers[key]; }); + if (m_quads && m_quads.imgQuads) { + m_quads.imgQuads.forEach(function (quad) { + if (quad.texture) { + delete quad.texture; + delete quad.image._texture; + } + }); + m_this._updateTextures(); + } + m_this.modified(); + }; + + /** + * Destroy. + */ + this._exit = function () { + m_this._cleanup(); s_exit.call(m_this); }; diff --git a/src/webgl/tileLayer.js b/src/webgl/tileLayer.js index 24f30ba550..4c4244a67f 100644 --- a/src/webgl/tileLayer.js +++ b/src/webgl/tileLayer.js @@ -157,9 +157,27 @@ var webgl_tileLayer = function () { }; /** - * Clean up the layer. + * Cleanup. This purges the texture and tile cache. + */ + this._cleanup = function () { + var tile; + if (m_this.cache && m_this.cache._cache) { + for (var hash in m_this.cache._cache) { + tile = m_this.cache._cache[hash]; + if (tile._image && tile._image._texture) { + delete tile._image._texture; + } + } + m_this.cache.clear(); + } + m_this.clear(); + }; + + /** + * Destroy. */ this._exit = function () { + m_this._cleanup(); m_this.deleteFeature(m_quadFeature); m_quadFeature = null; m_tiles = []; diff --git a/tests/cases/contourFeature.js b/tests/cases/contourFeature.js index 00844f80fe..f399802360 100644 --- a/tests/cases/contourFeature.js +++ b/tests/cases/contourFeature.js @@ -62,6 +62,10 @@ describe('Contour Feature', function () { expect(contour.contour.get('gridWidth')()).toBe(40); expect(contour.contour.get('gridHeight')()).toBe(5); }); + it('actors', function () { + var contour = geo.webgl.contourFeature({layer: layer}); + expect(contour.actors().length).toBe(1); + }); }); describe('Create contours', function () { diff --git a/tests/cases/feature.js b/tests/cases/feature.js index fa69cac85f..131b06e219 100644 --- a/tests/cases/feature.js +++ b/tests/cases/feature.js @@ -44,7 +44,7 @@ describe('geo.feature', function () { feat = geo.feature({layer: layer}); expect(feat.layer()).toBe(layer); - expect(feat.renderer()).toBe(null); + expect(feat.renderer()).toBe(layer.renderer()); }); }); describe('Check private class methods', function () { diff --git a/tests/cases/layer.js b/tests/cases/layer.js index cf478de7fe..ba1ba88402 100644 --- a/tests/cases/layer.js +++ b/tests/cases/layer.js @@ -1,7 +1,11 @@ // Test geo.layer +var $ = require('jquery'); var geo = require('../test-utils').geo; var createMap = require('../test-utils').createMap; +var destroyMap = require('../test-utils').destroyMap; +var mockWebglRenderer = geo.util.mockWebglRenderer; +var restoreWebglRenderer = geo.util.restoreWebglRenderer; describe('geo.layer', function () { 'use strict'; @@ -267,3 +271,190 @@ describe('geo.layer', function () { * layerReorder.js */ }); }); + +describe('geo.webgl.layer', function () { + 'use strict'; + describe('autoshareRenderer is false', function () { + var map, layer1, layer2, layer3; + it('_init', function (done) { + mockWebglRenderer(); + sinon.stub(console, 'log', function () {}); + map = createMap(); + layer1 = map.createLayer('osm', {renderer: 'webgl', url: '/testdata/white.jpg', autoshareRenderer: false}); + layer2 = map.createLayer('osm', {renderer: 'webgl', url: '/testdata/weather.png', keepLower: false, autoshareRenderer: false}); + layer3 = map.createLayer('feature', {renderer: 'webgl', autoshareRenderer: false}); + layer3.createFeature('point', {}).data([{x: 2, y: 1}]); + map.onIdle(function () { + expect($('canvas', map.node()).length).toBe(3); + done(); + }); + }); + it('visible', function () { + layer1.visible(false); + expect($('canvas', map.node()).length).toBe(3); + layer1.visible(true); + expect($('canvas', map.node()).length).toBe(3); + layer2.visible(false); + expect($('canvas', map.node()).length).toBe(3); + layer2.visible(true); + expect($('canvas', map.node()).length).toBe(3); + }); + it('opacity', function () { + layer1.opacity(0.5); + expect($('canvas', map.node()).length).toBe(3); + layer2.opacity(0.5); + expect($('canvas', map.node()).length).toBe(3); + layer1.opacity(1); + expect($('canvas', map.node()).length).toBe(3); + layer2.opacity(1); + expect($('canvas', map.node()).length).toBe(3); + }); + it('zIndex', function () { + layer1.moveUp(); + expect($('canvas', map.node()).length).toBe(3); + layer1.moveUp(); + expect($('canvas', map.node()).length).toBe(3); + layer1.moveToBottom(); + expect($('canvas', map.node()).length).toBe(3); + }); + it('cleanup', function () { + console.log.restore(); + destroyMap(); + restoreWebglRenderer(); + }); + }); + describe('autoshareRenderer is true', function () { + var map, layer1, layer2, layer3; + it('_init', function (done) { + mockWebglRenderer(); + sinon.stub(console, 'log', function () {}); + map = createMap(); + layer1 = map.createLayer('osm', {renderer: 'webgl', url: '/testdata/white.jpg'}); + layer2 = map.createLayer('osm', {renderer: 'webgl', url: '/testdata/weather.png', keepLower: false}); + layer3 = map.createLayer('feature', {renderer: 'webgl'}); + layer3.createFeature('point', {}).data([{x: 2, y: 1}]); + map.onIdle(function () { + expect($('canvas', map.node()).length).toBe(1); + done(); + }); + }); + it('visible', function () { + layer1.visible(false); + expect($('canvas', map.node()).length).toBe(1); + layer1.visible(true); + expect($('canvas', map.node()).length).toBe(1); + layer2.visible(false); + expect($('canvas', map.node()).length).toBe(1); + layer2.visible(true); + expect($('canvas', map.node()).length).toBe(1); + }); + it('opacity', function () { + layer1.opacity(0.5); + expect($('canvas', map.node()).length).toBe(2); + layer2.opacity(0.5); + expect($('canvas', map.node()).length).toBe(2); + layer1.opacity(1); + expect($('canvas', map.node()).length).toBe(3); + layer2.opacity(1); + expect($('canvas', map.node()).length).toBe(1); + }); + it('zIndex', function () { + expect($('canvas', layer1.node()).length).toBe(1); + expect($('canvas', layer2.node()).length).toBe(0); + expect($('canvas', layer3.node()).length).toBe(0); + layer1.moveUp(); + expect($('canvas', map.node()).length).toBe(1); + expect($('canvas', layer1.node()).length).toBe(0); + expect($('canvas', layer2.node()).length).toBe(1); + expect($('canvas', layer3.node()).length).toBe(0); + layer1.moveUp(); + expect($('canvas', map.node()).length).toBe(1); + expect($('canvas', layer1.node()).length).toBe(0); + expect($('canvas', layer2.node()).length).toBe(1); + expect($('canvas', layer3.node()).length).toBe(0); + layer1.moveToBottom(); + expect($('canvas', map.node()).length).toBe(1); + expect($('canvas', layer1.node()).length).toBe(1); + expect($('canvas', layer2.node()).length).toBe(0); + expect($('canvas', layer3.node()).length).toBe(0); + }); + it('cleanup', function () { + console.log.restore(); + destroyMap(); + restoreWebglRenderer(); + }); + }); + describe('autoshareRenderer is mixed', function () { + var map, layer1, layer2, layer3; + it('_init', function (done) { + mockWebglRenderer(); + sinon.stub(console, 'log', function () {}); + map = createMap(); + layer1 = map.createLayer('osm', {renderer: 'webgl', url: '/testdata/white.jpg'}); + layer2 = map.createLayer('osm', {renderer: 'webgl', url: '/testdata/weather.png', keepLower: false, autoshareRenderer: false}); + layer3 = map.createLayer('feature', {renderer: 'webgl'}); + layer3.createFeature('point', {}).data([{x: 2, y: 1}]); + map.onIdle(function () { + expect($('canvas', map.node()).length).toBe(3); + done(); + }); + }); + it('zIndex', function () { + expect($('canvas', layer1.node()).length).toBe(1); + expect($('canvas', layer2.node()).length).toBe(1); + expect($('canvas', layer3.node()).length).toBe(1); + layer1.moveUp(); + expect($('canvas', map.node()).length).toBe(2); + expect($('canvas', layer1.node()).length).toBe(1); + expect($('canvas', layer2.node()).length).toBe(1); + expect($('canvas', layer3.node()).length).toBe(0); + layer1.moveUp(); + expect($('canvas', map.node()).length).toBe(2); + expect($('canvas', layer1.node()).length).toBe(0); + expect($('canvas', layer2.node()).length).toBe(1); + expect($('canvas', layer3.node()).length).toBe(1); + layer1.moveToBottom(); + expect($('canvas', map.node()).length).toBe(3); + expect($('canvas', layer1.node()).length).toBe(1); + expect($('canvas', layer2.node()).length).toBe(1); + expect($('canvas', layer3.node()).length).toBe(1); + }); + it('cleanup', function () { + console.log.restore(); + destroyMap(); + restoreWebglRenderer(); + }); + }); + describe('manually share renderers', function () { + var map, layer1, layer2, layer3; + it('_init', function (done) { + mockWebglRenderer(); + sinon.stub(console, 'log', function () {}); + map = createMap(); + layer1 = map.createLayer('osm', {renderer: 'webgl', url: '/testdata/white.jpg', autoshareRenderer: false}); + layer2 = map.createLayer('osm', {url: '/testdata/weather.png', keepLower: false, autoshareRenderer: false, renderer: layer1.renderer()}); + layer3 = map.createLayer('feature', {autoshareRenderer: false, renderer: layer1.renderer()}); + layer3.createFeature('point', {}).data([{x: 2, y: 1}]); + map.onIdle(function () { + expect($('canvas', map.node()).length).toBe(1); + done(); + }); + }); + it('switchRenderers', function () { + var r = geo.createRenderer('webgl', layer2); + layer2.switchRenderer(r, true); + expect($('canvas', map.node()).length).toBe(2); + layer1.switchRenderer(r, true); + expect($('canvas', map.node()).length).toBe(2); + layer3.switchRenderer(r, true); + expect($('canvas', map.node()).length).toBe(2); + $('canvas', layer1.node()).remove(); + expect($('canvas', map.node()).length).toBe(1); + }); + it('cleanup', function () { + console.log.restore(); + destroyMap(); + restoreWebglRenderer(); + }); + }); +}); diff --git a/tests/cases/polygonFeature.js b/tests/cases/polygonFeature.js index ae0cf4f2bb..2c71822371 100644 --- a/tests/cases/polygonFeature.js +++ b/tests/cases/polygonFeature.js @@ -307,6 +307,16 @@ describe('geo.polygonFeature', function () { restoreWebglRenderer(); }); }); + it('actors', function () { + mockWebglRenderer(); + var map, layer, polygon; + + map = createMap(); + layer = map.createLayer('feature', {renderer: 'webgl'}); + polygon = geo.webgl.polygonFeature({layer: layer}); + expect(polygon.actors().length).toBe(1); + restoreWebglRenderer(); + }); }); describe('Private utility methods', function () { diff --git a/tests/cases/quadFeature.js b/tests/cases/quadFeature.js index 8500402ff6..8a939255be 100644 --- a/tests/cases/quadFeature.js +++ b/tests/cases/quadFeature.js @@ -529,6 +529,7 @@ describe('geo.quadFeature', function () { quads.data([testQuads[0], testQuads[1]]); map.draw(); expect(buildTime).not.toEqual(quads.buildTime().timestamp()); + expect(quads.actors().length).toBe(2); }); waitForIt('next render gl B', function () { return vgl.mockCounts().activeTexture >= glCounts.activeTexture + 2 && @@ -541,6 +542,7 @@ describe('geo.quadFeature', function () { quads.data([testQuads[8], testQuads[9]]); map.draw(); expect(buildTime).not.toEqual(quads.buildTime().timestamp()); + expect(quads.actors().length).toBe(2); }); waitForIt('next render gl C', function () { return vgl.mockCounts().activeTexture === glCounts.activeTexture && @@ -556,6 +558,7 @@ describe('geo.quadFeature', function () { } quads.data(data); map.draw(); + expect(quads.actors().length).toBe(2); }); waitForIt('next render gl D', function () { return vgl.mockCounts().deleteBuffer >= (glCounts.deleteBuffer || 0) + 2 && diff --git a/tests/data/weather.png b/tests/data/weather.png new file mode 100644 index 0000000000000000000000000000000000000000..b225f20cbbec0eaf4d46caf620d836493a0c2446 GIT binary patch literal 6448 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