Add an indexModified method to allow more efficient updates.
MODIFY
src/canvas/pixelmapFeature.js
src/canvas/pixelmapFeature.js
@@ -135,11 +135,13 @@ var canvas_pixelmapFeature = function (arg) {
updateFirst, updateLast = -1, update, prepared;
if (!m_this.m_info) {
+ m_this.indexModified(undefined, 'clear');
if (!m_this._preparePixelmap()) {
return;
}
prepared = true;
}
+ m_this.indexModified(undefined, 'clear');
mappedColors = m_this.m_info.mappedColors;
updateFirst = m_this.m_info.area;
for (idx in mappedColors) {

MODIFY
src/pixelmapFeature.js
src/pixelmapFeature.js
@@ -53,6 +53,7 @@ var pixelmapFeature = function (arg) {
*/
var m_this = this,
s_update = this._update,
+ m_modifiedIndexRange,
s_init = this._init;
this.featureType = 'pixelmap';
@@ -115,6 +116,42 @@ var pixelmapFeature = function (arg) {
return m_this;
};
+ /**
+ * Mark that an index's data value (and hence its color) has changed without
+ * marking all of the data array as changed. If this function is called
+ * without any parameters, it clears the tracked changes.
+ *
+ * @param {integer} [idx] The lowest data index that has changed. If
+ * `undefined`, return the current tracked changed range.
+ * @param {integer|'clear'} [idx2] If an index was specified in `idx` and
+ * this is specified, the highest index (inclusive) that has changed. If
+ * returning the tracked changed range and this is `clear`, clear the
+ * tracked range.
+ * @returns {this|integer[]} When returning a range, this is the lowest and
+ * highest index values that have changed (inclusive), so their range is
+ * `[0, data.length)`.
+ */
+ this.indexModified = function (idx, idx2) {
+ if (idx === undefined) {
+ const range = m_modifiedIndexRange;
+ if (idx2 === 'clear') {
+ m_modifiedIndexRange = undefined;
+ }
+ return range;
+ }
+ m_this.modified();
+ if (m_modifiedIndexRange === undefined) {
+ m_modifiedIndexRange = [idx, idx];
+ }
+ if (idx < m_modifiedIndexRange[0]) {
+ m_modifiedIndexRange[0] = idx;
+ }
+ if ((idx2 || idx) > m_modifiedIndexRange[1]) {
+ m_modifiedIndexRange[1] = (idx2 || idx);
+ }
+ return m_this;
+ };
+
/**
* Update.
*
@@ -173,7 +210,6 @@ var pixelmapFeature = function (arg) {
* drawing a quad, which can trigger a full layer update, which in turn
* checks if this feature is built. Setting the build time avoids calling
* this a second time. */
- m_this.buildTime().modified();
if (!m_this.m_srcImage) {
var src = m_this.style.get('url')();
if (util.isReadyImage(src)) {
@@ -224,6 +260,7 @@ var pixelmapFeature = function (arg) {
} else if (m_this.m_info) {
m_this._computePixelmap();
}
+ m_this.buildTime().modified();
return m_this;
};

MODIFY
src/pixelmapLayer.js
src/pixelmapLayer.js
@@ -102,6 +102,7 @@ var pixelmapLayer = function (arg) {
}
m_this.style = m_pixelmapFeature.style;
m_this.data = m_pixelmapFeature.data;
+ m_this.indexModified = m_pixelmapFeature.indexModified;
const s_dataTimeModified = m_this.dataTime().modified;
m_this.dataTime().modified = () => {
m_pixelmapFeature.dataTime().modified();

MODIFY
src/webgl/pixelmapFeature.js
src/webgl/pixelmapFeature.js
@@ -82,34 +82,49 @@ var webgl_pixelmapFeature = function (arg) {
var data = m_this.data() || [],
colorFunc = m_this.style.get('color');
+ let indexRange = m_this.indexModified(undefined, 'clear');
+ let fullUpdate = m_this.dataTime().timestamp() >= m_this.buildTime().timestamp() || indexRange === undefined;
if (!m_lookupTable) {
m_lookupTable = lookupTable2D();
m_lookupTable.setTextureUnit(1);
+ fullUpdate = true;
}
let clrLen = Math.max(1, data.length);
const maxWidth = m_lookupTable.maxWidth();
if (clrLen > maxWidth && clrLen % maxWidth) {
clrLen += maxWidth - (clrLen % maxWidth);
}
- const colors = new Uint8Array(clrLen * 4);
- data.forEach((d, i) => {
+
+ let colors;
+ if (!fullUpdate) {
+ colors = m_lookupTable.colorTable();
+ fullUpdate = colors.length !== clrLen * 4;
+ indexRange[0] = Math.max(0, indexRange[0]);
+ indexRange[1] = Math.min(data.length, indexRange[1] + 1);
+ }
+ if (fullUpdate) {
+ colors = new Uint8Array(clrLen * 4);
+ indexRange = [0, data.length];
+ }
+ for (let i = indexRange[0]; i < indexRange[1]; i += 1) {
+ const d = data[i];
const color = util.convertColor(colorFunc.call(m_this, d, i));
colors[i * 4] = color.r * 255;
colors[i * 4 + 1] = color.g * 255;
colors[i * 4 + 2] = color.b * 255;
colors[i * 4 + 3] = color.a === undefined ? 255 : (color.a * 255);
- });
+ }
m_this.m_info = {colors: colors};
// check if colors haven't changed
var oldcolors = m_lookupTable.colorTable();
if (oldcolors && oldcolors.length === colors.length) {
- let idx = 0;
- for (; idx < colors.length; idx += 1) {
+ let idx = indexRange[0] * 4;
+ for (; idx < indexRange[1] * 4; idx += 1) {
if (colors[idx] !== oldcolors[idx]) {
break;
}
}
- if (idx === colors.length) {
+ if (idx === indexRange[1] * 4) {
return;
}
}

MODIFY
src/webgl/quadFeature.js
src/webgl/quadFeature.js
@@ -371,7 +371,9 @@ var webgl_quadFeature = function (arg) {
return;
}
quad.texture.bind(renderState);
- if (context.getError() === context.OUT_OF_MEMORY) {
+ // only check if the context is out of memory when using modestly large
+ // textures. The check is slow.
+ if ((quad.image.width > 4096 || quad.image.height > 4096 || quad.image.width * quad.image.height > 4194304) && context.getError() === context.OUT_OF_MEMORY) {
console.log('Insufficient GPU memory for texture');
}
if (quad.opacity !== opacity) {

MODIFY
tests/gl-cases/pixelmapLayer.js
tests/gl-cases/pixelmapLayer.js
@@ -54,6 +54,21 @@ describe('webglPixelmapLayer', function () {
createPixelmap({style: undefined, color: 'black'});
expect(layer.style.get('color')(0, 0)).toEqual({r: 0, b: 0, g: 0});
});
+ it('indexModified', function (done) {
+ createPixelmap();
+ expect(layer.indexModified()).toBe(undefined);
+ layer.indexModified(2);
+ expect(layer.indexModified()).toEqual([2, 2]);
+ layer.indexModified(4);
+ expect(layer.indexModified()).toEqual([2, 4]);
+ layer.indexModified(1, 3);
+ expect(layer.indexModified()).toEqual([1, 4]);
+ layer.indexModified(undefined, 'clear');
+ expect(layer.indexModified()).toBe(undefined);
+ layer.indexModified(2);
+ layer.draw();
+ map.onIdle(done);
+ });
it('geoOn and geoOff', function (done) {
createPixelmap();
var click = false;

MODIFY
tutorials/tiled-pixelmap/index.pug
tutorials/tiled-pixelmap/index.pug
@@ -75,6 +75,19 @@ block mainTutorial
coloring so that superpixels start transparent with yellow edges and cycle
through red, green, and blue as they are selected.
+ When changing a value in the data array, we need to inform the pixelmap
+ that it is changed. This could be done a variety of ways:
+
+ - `pixelmap.data(data)` - this marks the entire data set as changed and
+ will require recomputing the most values.
+
+ - `pixelmap.modified()` - this states that the style has changed in some
+ way, but without more specificifity, all of the colors are rechecked.
+
+ - `pixelmap.indexModified(lowIndex, highIndex)` - this states that only a
+ small number of indicies' colors have changed, allowing the most
+ efficient update.
+
+codeblock('javascript', 3, 2).
pixelmap.style('color', (d, i) => {
switch (d) {
@@ -91,9 +104,9 @@ block mainTutorial
var data = pixelmap.data();
// change the value of the superpixel and its border
data[index] = data[index + 1] = (data[index] + 1) % 4;
- // since we changed the data directory, just tell the layer that it has
- // been modified and to redraw.
- pixelmap.modified().draw();
+ // since we changed the data directly, tell the layer that it has been
+ // modified and to redraw. Use `indexModified` for efficiency.
+ pixelmap.indexModified(index, index + 1).draw();
});
:markdown-it
@@ -121,11 +134,11 @@ block mainTutorial
if (newValue === undefined) {
data[index] = data[index + 1] = (data[index] + 1) % 4;
newValue = data[index];
- pixelmap.modified().draw();
+ pixelmap.indexModified(index, index + 1).draw();
}
// if we are moving and things have changed, update
if (data[index] !== newValue) {
data[index] = data[index + 1] = newValue;
- pixelmap.modified().draw();
+ pixelmap.indexModified(index, index + 1).draw();
}
});

MODIFY
tutorials/tiled-pixelmap/tutorial.json
tutorials/tiled-pixelmap/tutorial.json
{
- "title": "Tile Pixelmap",
+ "title": "Tiled Pixelmap",
"hideNavbar": true,
"level": 1,
"tutorialCss": [],

Add an indexModified method to allow more efficient updates.