Merge pull request #418 from twpayne/webgl-core-improvements

WebGL core improvements
This commit is contained in:
Tom Payne
2013-03-23 05:55:23 -07:00
13 changed files with 1754 additions and 287 deletions

View File

@@ -36,15 +36,20 @@ ol.Extent.boundingExtent = function(var_args) {
var i;
for (i = 1; i < arguments.length; ++i) {
var coordinate = arguments[i];
extent.minX = Math.min(extent.minX, coordinate.x);
extent.minY = Math.min(extent.minY, coordinate.y);
extent.maxX = Math.max(extent.maxX, coordinate.x);
extent.maxY = Math.max(extent.maxY, coordinate.y);
extent.extendXY(coordinate.x, coordinate.y);
}
return extent;
};
/**
* @return {ol.Extent} Empty extent.
*/
ol.Extent.createEmptyExtent = function() {
return new ol.Extent(Infinity, Infinity, -Infinity, -Infinity);
};
/**
* @param {ol.Coordinate} center Center.
* @param {number} resolution Resolution.

View File

@@ -29,6 +29,16 @@ ol.math.csch = function(x) {
};
/**
* @param {number} x X.
* @return {number} The smallest power of two greater than or equal to x.
*/
ol.math.roundUpToPowerOfTwo = function(x) {
goog.asserts.assert(0 < x);
return Math.pow(2, Math.ceil(Math.log(x) / Math.LN2));
};
/**
* @param {number} x X.
* @return {number} Hyperbolic secant of x.

View File

@@ -15,9 +15,6 @@ goog.require('ol.Size');
*/
ol.Rectangle = function(minX, minY, maxX, maxY) {
goog.asserts.assert(minX <= maxX);
goog.asserts.assert(minY <= maxY);
/**
* @type {number}
*/
@@ -55,10 +52,38 @@ ol.Rectangle.prototype.equals = function(rectangle) {
* @param {ol.Rectangle} rectangle Rectangle.
*/
ol.Rectangle.prototype.extend = function(rectangle) {
this.minX = Math.min(this.minX, rectangle.minX);
this.minY = Math.min(this.minY, rectangle.minY);
this.maxX = Math.max(this.maxX, rectangle.maxX);
this.maxY = Math.max(this.maxY, rectangle.maxY);
if (rectangle.minX < this.minX) {
this.minX = rectangle.minX;
}
if (rectangle.minY < this.minY) {
this.minY = rectangle.minY;
}
if (rectangle.maxX > this.maxX) {
this.maxX = rectangle.maxX;
}
if (rectangle.maxY > this.maxY) {
this.maxY = rectangle.maxY;
}
};
/**
* @param {number} x X.
* @param {number} y Y.
*/
ol.Rectangle.prototype.extendXY = function(x, y) {
if (x < this.minX) {
this.minX = x;
}
if (y < this.minY) {
this.minY = y;
}
if (x > this.maxX) {
this.maxX = x;
}
if (y > this.maxY) {
this.maxY = y;
}
};
@@ -107,6 +132,14 @@ ol.Rectangle.prototype.intersects = function(rectangle) {
};
/**
* @return {boolean} Is empty.
*/
ol.Rectangle.prototype.isEmpty = function() {
return this.maxX < this.minX || this.maxY < this.minY;
};
/**
* @param {ol.Coordinate} coordinate Coordinate.
* @return {ol.Coordinate} Coordinate.

View File

@@ -28,25 +28,6 @@ ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) {
*/
this.image_ = null;
/**
* The last rendered texture.
* @private
* @type {WebGLTexture}
*/
this.texture_ = null;
/**
* @private
* @type {!goog.vec.Mat4.Number}
*/
this.texCoordMatrix_ = goog.vec.Mat4.createNumberIdentity();
/**
* @private
* @type {!goog.vec.Mat4.Number}
*/
this.projectionMatrix_ = goog.vec.Mat4.createNumber();
};
goog.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer);
@@ -63,7 +44,7 @@ ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {
// http://learningwebgl.com/blog/?p=2101
var imageElement = image.getImageElement(this);
var gl = this.getMapRenderer().getGL();
var gl = this.getWebGLMapRenderer().getGL();
var texture = gl.createTexture();
@@ -86,43 +67,6 @@ ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {
};
/**
* @inheritDoc
*/
ol.renderer.webgl.ImageLayer.prototype.disposeInternal = function() {
var mapRenderer = this.getMapRenderer();
var gl = mapRenderer.getGL();
if (!gl.isContextLost()) {
gl.deleteTexture(this.texture_);
}
goog.base(this, 'disposeInternal');
};
/**
* @inheritDoc
*/
ol.renderer.webgl.ImageLayer.prototype.getTexCoordMatrix = function() {
return this.texCoordMatrix_;
};
/**
* @inheritDoc
*/
ol.renderer.webgl.ImageLayer.prototype.getTexture = function() {
return this.texture_;
};
/**
* @inheritDoc
*/
ol.renderer.webgl.ImageLayer.prototype.getProjectionMatrix = function() {
return this.projectionMatrix_;
};
/**
* @return {ol.layer.ImageLayer} Tile layer.
*/
@@ -131,21 +75,13 @@ ol.renderer.webgl.ImageLayer.prototype.getImageLayer = function() {
};
/**
* @inheritDoc
*/
ol.renderer.webgl.ImageLayer.prototype.handleWebGLContextLost = function() {
this.texture_ = null;
};
/**
* @inheritDoc
*/
ol.renderer.webgl.ImageLayer.prototype.renderFrame =
function(frameState, layerState) {
var gl = this.getMapRenderer().getGL();
var gl = this.getWebGLMapRenderer().getGL();
var view2DState = frameState.view2DState;
var viewCenter = view2DState.center;
@@ -153,7 +89,7 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame =
var viewRotation = view2DState.rotation;
var image = this.image_;
var texture = this.texture_;
var texture = this.texture;
var imageLayer = this.getImageLayer();
var imageSource = imageLayer.getImageSource();
@@ -171,13 +107,13 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame =
} else if (imageState == ol.ImageState.LOADED) {
image = image_;
texture = this.createTexture_(image_);
if (!goog.isNull(this.texture_)) {
if (!goog.isNull(this.texture)) {
frameState.postRenderFunctions.push(
goog.partial(function(gl, texture) {
if (!gl.isContextLost()) {
gl.deleteTexture(texture);
}
}, gl, this.texture_));
}, gl, this.texture));
}
}
}
@@ -186,19 +122,19 @@ ol.renderer.webgl.ImageLayer.prototype.renderFrame =
if (!goog.isNull(image)) {
goog.asserts.assert(!goog.isNull(texture));
var canvas = this.getMapRenderer().getCanvas();
var canvas = this.getWebGLMapRenderer().getCanvas();
this.updateProjectionMatrix_(canvas.width, canvas.height,
viewCenter, viewResolution, viewRotation, image.getExtent());
// Translate and scale to flip the Y coord.
var texCoordMatrix = this.texCoordMatrix_;
var texCoordMatrix = this.texCoordMatrix;
goog.vec.Mat4.makeIdentity(texCoordMatrix);
goog.vec.Mat4.scale(texCoordMatrix, 1, -1, 1);
goog.vec.Mat4.translate(texCoordMatrix, 0, -1, 0);
this.image_ = image;
this.texture_ = texture;
this.texture = texture;
this.updateAttributions(frameState.attributions, image.getAttributions());
}
@@ -221,7 +157,7 @@ ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ =
var canvasExtentWidth = canvasWidth * viewResolution;
var canvasExtentHeight = canvasHeight * viewResolution;
var projectionMatrix = this.projectionMatrix_;
var projectionMatrix = this.projectionMatrix;
goog.vec.Mat4.makeIdentity(projectionMatrix);
goog.vec.Mat4.scale(projectionMatrix,
2 / canvasExtentWidth, 2 / canvasExtentHeight, 1);

View File

@@ -3,6 +3,7 @@
goog.provide('ol.renderer.webgl.Layer');
goog.require('goog.vec.Mat4');
goog.require('ol.FrameState');
goog.require('ol.layer.Layer');
goog.require('ol.renderer.Layer');
goog.require('ol.vec.Mat4');
@@ -19,6 +20,36 @@ ol.renderer.webgl.Layer = function(mapRenderer, layer) {
goog.base(this, mapRenderer, layer);
/**
* @protected
* @type {WebGLTexture}
*/
this.texture = null;
/**
* @protected
* @type {WebGLFramebuffer}
*/
this.framebuffer = null;
/**
* @protected
* @type {number|undefined}
*/
this.framebufferDimension = undefined;
/**
* @protected
* @type {!goog.vec.Mat4.Number}
*/
this.texCoordMatrix = goog.vec.Mat4.createNumber();
/**
* @protected
* @type {!goog.vec.Mat4.Number}
*/
this.projectionMatrix = goog.vec.Mat4.createNumberIdentity();
/**
* @private
* @type {!goog.vec.Mat4.Float32}
@@ -64,6 +95,55 @@ ol.renderer.webgl.Layer = function(mapRenderer, layer) {
goog.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer);
/**
* @param {ol.FrameState} frameState Frame state.
* @param {number} framebufferDimension Framebuffer dimension.
* @protected
*/
ol.renderer.webgl.Layer.prototype.bindFramebuffer =
function(frameState, framebufferDimension) {
var mapRenderer = this.getWebGLMapRenderer();
var gl = mapRenderer.getGL();
if (!goog.isDef(this.framebufferDimension) ||
this.framebufferDimension != framebufferDimension) {
var map = this.getMap();
frameState.postRenderFunctions.push(
goog.partial(function(gl, framebuffer, texture) {
if (!gl.isContextLost()) {
gl.deleteFramebuffer(framebuffer);
gl.deleteTexture(texture);
}
}, gl, this.framebuffer, this.texture));
var texture = gl.createTexture();
gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA,
goog.webgl.UNSIGNED_BYTE, null);
gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER,
goog.webgl.LINEAR);
gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER,
goog.webgl.LINEAR);
var framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER,
goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0);
this.texture = texture;
this.framebuffer = framebuffer;
this.framebufferDimension = framebufferDimension;
} else {
gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer);
}
};
/**
* @return {!goog.vec.Mat4.Float32} Color matrix.
*/
@@ -76,31 +156,35 @@ ol.renderer.webgl.Layer.prototype.getColorMatrix = function() {
/**
* @inheritDoc
* @return {ol.renderer.Map} MapRenderer.
* @return {ol.renderer.webgl.Map} MapRenderer.
*/
ol.renderer.webgl.Layer.prototype.getMapRenderer = function() {
return /** @type {ol.renderer.webgl.Map} */ (goog.base(
this, 'getMapRenderer'));
ol.renderer.webgl.Layer.prototype.getWebGLMapRenderer = function() {
return /** @type {ol.renderer.webgl.Map} */ (this.getMapRenderer());
};
/**
* @return {!goog.vec.Mat4.Number} Matrix.
*/
ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = goog.abstractMethod;
ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() {
return this.texCoordMatrix;
};
/**
* @return {WebGLTexture} Texture.
*/
ol.renderer.webgl.Layer.prototype.getTexture = goog.abstractMethod;
ol.renderer.webgl.Layer.prototype.getTexture = function() {
return this.texture;
};
/**
* @return {!goog.vec.Mat4.Number} Matrix.
*/
ol.renderer.webgl.Layer.prototype.getProjectionMatrix = goog.abstractMethod;
ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() {
return this.projectionMatrix;
};
/**
@@ -150,7 +234,11 @@ ol.renderer.webgl.Layer.prototype.handleLayerSaturationChange = function() {
/**
* Handle webglcontextlost.
*/
ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = goog.nullFunction;
ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() {
this.texture = null;
this.framebuffer = null;
this.framebufferDimension = undefined;
};
/**

View File

@@ -22,6 +22,8 @@ goog.require('ol.renderer.webgl.FragmentShader');
goog.require('ol.renderer.webgl.ImageLayer');
goog.require('ol.renderer.webgl.TileLayer');
goog.require('ol.renderer.webgl.VertexShader');
goog.require('ol.structs.Buffer');
goog.require('ol.structs.IntegerSet');
goog.require('ol.structs.LRUCache');
goog.require('ol.webgl');
goog.require('ol.webgl.WebGLContextEventType');
@@ -33,6 +35,14 @@ goog.require('ol.webgl.WebGLContextEventType');
ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK = 1024;
/**
* @typedef {{buf: ol.structs.Buffer,
* buffer: WebGLBuffer,
* dirtySet: ol.structs.IntegerSet}}
*/
ol.renderer.webgl.BufferCacheEntry;
/**
* @typedef {{magFilter: number, minFilter: number, texture: WebGLTexture}}
*/
@@ -170,9 +180,20 @@ ol.renderer.webgl.Map = function(container, map) {
/**
* @private
* @type {WebGLBuffer}
* @type {ol.structs.Buffer}
*/
this.arrayBuffer_ = null;
this.arrayBuffer_ = new ol.structs.Buffer([
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
1, 1, 1, 1
]);
/**
* @private
* @type {Object.<number, ol.renderer.webgl.BufferCacheEntry>}
*/
this.bufferCache_ = {};
/**
* @private
@@ -216,6 +237,47 @@ ol.renderer.webgl.Map = function(container, map) {
goog.inherits(ol.renderer.webgl.Map, ol.renderer.Map);
/**
* @param {number} target Target.
* @param {ol.structs.Buffer} buf Buffer.
*/
ol.renderer.webgl.Map.prototype.bindBuffer = function(target, buf) {
var gl = this.getGL();
var arr = buf.getArray();
var bufferKey = goog.getUid(buf);
if (bufferKey in this.bufferCache_) {
var bufferCacheEntry = this.bufferCache_[bufferKey];
gl.bindBuffer(target, bufferCacheEntry.buffer);
bufferCacheEntry.dirtySet.forEachRange(function(start, stop) {
// FIXME check if slice is really efficient here
var slice = arr.slice(start, stop);
gl.bufferSubData(
target,
start,
target == goog.webgl.ARRAY_BUFFER ?
new Float32Array(slice) :
new Uint16Array(slice));
});
bufferCacheEntry.dirtySet.clear();
} else {
var buffer = gl.createBuffer();
gl.bindBuffer(target, buffer);
gl.bufferData(
target,
target == goog.webgl.ARRAY_BUFFER ?
new Float32Array(arr) : new Uint16Array(arr),
buf.getUsage());
var dirtySet = new ol.structs.IntegerSet();
buf.addDirtySet(dirtySet);
this.bufferCache_[bufferKey] = {
buf: buf,
buffer: buffer,
dirtySet: dirtySet
};
}
};
/**
* @param {ol.Tile} tile Tile.
* @param {number} magFilter Mag filter.
@@ -276,12 +338,35 @@ ol.renderer.webgl.Map.prototype.createLayerRenderer = function(layer) {
};
/**
* @param {ol.structs.Buffer} buf Buffer.
*/
ol.renderer.webgl.Map.prototype.deleteBuffer = function(buf) {
var gl = this.getGL();
var arr = buf.getArray();
var bufferKey = goog.getUid(buf);
goog.asserts.assert(bufferKey in this.bufferCache_);
var bufferCacheEntry = this.bufferCache_[bufferKey];
bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet);
if (!gl.isContextLost()) {
gl.deleteBuffer(bufferCacheEntry.buffer);
}
delete this.bufferCache_[bufferKey];
};
/**
* @inheritDoc
*/
ol.renderer.webgl.Map.prototype.disposeInternal = function() {
var gl = this.getGL();
goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet);
});
if (!gl.isContextLost()) {
goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) {
gl.deleteBuffer(bufferCacheEntry.buffer);
});
goog.object.forEach(this.programCache_, function(program) {
gl.deleteProgram(program);
});
@@ -409,7 +494,7 @@ ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) {
}
event.preventDefault();
this.locations_ = null;
this.arrayBuffer_ = null;
this.bufferCache_ = {};
this.shaderCache_ = {};
this.programCache_ = {};
this.textureCache_.clear();
@@ -511,19 +596,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
};
}
if (goog.isNull(this.arrayBuffer_)) {
var arrayBuffer = gl.createBuffer();
gl.bindBuffer(goog.webgl.ARRAY_BUFFER, arrayBuffer);
gl.bufferData(goog.webgl.ARRAY_BUFFER, new Float32Array([
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
1, 1, 1, 1
]), goog.webgl.STATIC_DRAW);
this.arrayBuffer_ = arrayBuffer;
} else {
gl.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_);
}
this.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_);
gl.enableVertexAttribArray(this.locations_.a_position);
gl.vertexAttribPointer(

View File

@@ -2,9 +2,7 @@
// FIXME animated shaders! check in redraw
goog.provide('ol.renderer.webgl.TileLayer');
goog.provide('ol.renderer.webgl.tilelayerrenderer');
goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Fragment');
goog.provide('ol.renderer.webgl.tilelayerrenderer.shader.Vertex');
goog.provide('ol.renderer.webgl.tilelayer.shader');
goog.require('goog.array');
goog.require('goog.object');
@@ -13,16 +11,17 @@ goog.require('goog.vec.Mat4');
goog.require('goog.vec.Vec4');
goog.require('goog.webgl');
goog.require('ol.Extent');
goog.require('ol.FrameState');
goog.require('ol.Size');
goog.require('ol.Tile');
goog.require('ol.TileCoord');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.layer.TileLayer');
goog.require('ol.math');
goog.require('ol.renderer.webgl.FragmentShader');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.VertexShader');
goog.require('ol.structs.Buffer');
@@ -30,23 +29,23 @@ goog.require('ol.renderer.webgl.VertexShader');
* @constructor
* @extends {ol.renderer.webgl.FragmentShader}
*/
ol.renderer.webgl.tilelayerrenderer.shader.Fragment = function() {
ol.renderer.webgl.tilelayer.shader.Fragment = function() {
goog.base(this, [
'precision mediump float;',
'',
'uniform sampler2D uTexture;',
'uniform sampler2D u_texture;',
'',
'varying vec2 vTexCoord;',
'varying vec2 v_texCoord;',
'',
'void main(void) {',
' gl_FragColor = texture2D(uTexture, vTexCoord);',
' gl_FragColor = texture2D(u_texture, v_texCoord);',
'}'
].join('\n'));
};
goog.inherits(
ol.renderer.webgl.tilelayerrenderer.shader.Fragment,
ol.renderer.webgl.tilelayer.shader.Fragment,
ol.renderer.webgl.FragmentShader);
goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Fragment);
goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Fragment);
@@ -54,27 +53,27 @@ goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Fragment);
* @constructor
* @extends {ol.renderer.webgl.VertexShader}
*/
ol.renderer.webgl.tilelayerrenderer.shader.Vertex = function() {
ol.renderer.webgl.tilelayer.shader.Vertex = function() {
goog.base(this, [
'attribute vec2 aPosition;',
'attribute vec2 aTexCoord;',
'attribute vec2 a_position;',
'attribute vec2 a_texCoord;',
'',
'varying vec2 vTexCoord;',
'varying vec2 v_texCoord;',
'',
'uniform vec4 uTileOffset;',
'uniform vec4 u_tileOffset;',
'',
'void main(void) {',
' gl_Position.xy = aPosition * uTileOffset.xy + uTileOffset.zw;',
' gl_Position.xy = a_position * u_tileOffset.xy + u_tileOffset.zw;',
' gl_Position.z = 0.;',
' gl_Position.w = 1.;',
' vTexCoord = aTexCoord;',
' v_texCoord = a_texCoord;',
'}'
].join('\n'));
};
goog.inherits(
ol.renderer.webgl.tilelayerrenderer.shader.Vertex,
ol.renderer.webgl.tilelayer.shader.Vertex,
ol.renderer.webgl.VertexShader);
goog.addSingletonGetter(ol.renderer.webgl.tilelayerrenderer.shader.Vertex);
goog.addSingletonGetter(ol.renderer.webgl.tilelayer.shader.Vertex);
@@ -93,59 +92,33 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {
* @type {ol.renderer.webgl.FragmentShader}
*/
this.fragmentShader_ =
ol.renderer.webgl.tilelayerrenderer.shader.Fragment.getInstance();
ol.renderer.webgl.tilelayer.shader.Fragment.getInstance();
/**
* @private
* @type {ol.renderer.webgl.VertexShader}
*/
this.vertexShader_ =
ol.renderer.webgl.tilelayerrenderer.shader.Vertex.getInstance();
this.vertexShader_ = ol.renderer.webgl.tilelayer.shader.Vertex.getInstance();
/**
* @private
* @type {{aPosition: number,
* aTexCoord: number,
* uTileOffset: WebGLUniformLocation,
* uTexture: WebGLUniformLocation}|null}
* @type {{a_position: number,
* a_texCoord: number,
* u_tileOffset: WebGLUniformLocation,
* u_texture: WebGLUniformLocation}|null}
*/
this.locations_ = null;
/**
* @private
* @type {WebGLBuffer}
* @type {ol.structs.Buffer}
*/
this.arrayBuffer_ = null;
/**
* @private
* @type {WebGLTexture}
*/
this.texture_ = null;
/**
* @private
* @type {WebGLFramebuffer}
*/
this.framebuffer_ = null;
/**
* @private
* @type {number|undefined}
*/
this.framebufferDimension_ = undefined;
/**
* @private
* @type {!goog.vec.Mat4.Number}
*/
this.texCoordMatrix_ = goog.vec.Mat4.createNumber();
/**
* @private
* @type {!goog.vec.Mat4.Number}
*/
this.projectionMatrix_ = goog.vec.Mat4.createNumberIdentity();
this.arrayBuffer_ = new ol.structs.Buffer([
0, 0, 0, 1,
1, 0, 1, 1,
0, 1, 0, 0,
1, 1, 1, 0
]);
/**
* @private
@@ -163,94 +136,16 @@ ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {
goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);
/**
* @param {ol.FrameState} frameState Frame state.
* @param {number} framebufferDimension Framebuffer dimension.
* @private
*/
ol.renderer.webgl.TileLayer.prototype.bindFramebuffer_ =
function(frameState, framebufferDimension) {
var mapRenderer = this.getMapRenderer();
var gl = mapRenderer.getGL();
if (!goog.isDef(this.framebufferDimension_) ||
this.framebufferDimension_ != framebufferDimension) {
var map = this.getMap();
frameState.postRenderFunctions.push(
goog.partial(function(gl, framebuffer, texture) {
if (!gl.isContextLost()) {
gl.deleteFramebuffer(framebuffer);
gl.deleteTexture(texture);
}
}, gl, this.framebuffer_, this.texture_));
var texture = gl.createTexture();
gl.bindTexture(goog.webgl.TEXTURE_2D, texture);
gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA,
framebufferDimension, framebufferDimension, 0, goog.webgl.RGBA,
goog.webgl.UNSIGNED_BYTE, null);
gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER,
goog.webgl.LINEAR);
gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MIN_FILTER,
goog.webgl.LINEAR);
var framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(goog.webgl.FRAMEBUFFER,
goog.webgl.COLOR_ATTACHMENT0, goog.webgl.TEXTURE_2D, texture, 0);
this.texture_ = texture;
this.framebuffer_ = framebuffer;
this.framebufferDimension_ = framebufferDimension;
} else {
gl.bindFramebuffer(goog.webgl.FRAMEBUFFER, this.framebuffer_);
}
};
/**
* @inheritDoc
*/
ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
var mapRenderer = this.getMapRenderer();
var gl = mapRenderer.getGL();
if (!gl.isContextLost()) {
gl.deleteBuffer(this.arrayBuffer_);
gl.deleteFramebuffer(this.framebuffer_);
gl.deleteTexture(this.texture_);
}
var mapRenderer = this.getWebGLMapRenderer();
mapRenderer.deleteBuffer(this.arrayBuffer_);
goog.base(this, 'disposeInternal');
};
/**
* @inheritDoc
*/
ol.renderer.webgl.TileLayer.prototype.getTexCoordMatrix = function() {
return this.texCoordMatrix_;
};
/**
* @inheritDoc
*/
ol.renderer.webgl.TileLayer.prototype.getTexture = function() {
return this.texture_;
};
/**
* @inheritDoc
*/
ol.renderer.webgl.TileLayer.prototype.getProjectionMatrix = function() {
return this.projectionMatrix_;
};
/**
* @return {ol.layer.TileLayer} Tile layer.
*/
@@ -263,11 +158,8 @@ ol.renderer.webgl.TileLayer.prototype.getTileLayer = function() {
* @inheritDoc
*/
ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
goog.base(this, 'handleWebGLContextLost');
this.locations_ = null;
this.arrayBuffer_ = null;
this.texture_ = null;
this.framebuffer_ = null;
this.framebufferDimension_ = undefined;
};
@@ -277,7 +169,7 @@ ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
ol.renderer.webgl.TileLayer.prototype.renderFrame =
function(frameState, layerState) {
var mapRenderer = this.getMapRenderer();
var mapRenderer = this.getWebGLMapRenderer();
var gl = mapRenderer.getGL();
var view2DState = frameState.view2DState;
@@ -316,8 +208,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
var maxDimension = Math.max(
tileRangeSize.width * tileSize.width,
tileRangeSize.height * tileSize.height);
var framebufferDimension =
Math.pow(2, Math.ceil(Math.log(maxDimension) / Math.log(2)));
var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension);
var framebufferExtentSize = new ol.Size(
tileResolution * framebufferDimension,
tileResolution * framebufferDimension);
@@ -330,7 +221,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
minX + framebufferExtentSize.width,
minY + framebufferExtentSize.height);
this.bindFramebuffer_(frameState, framebufferDimension);
this.bindFramebuffer(frameState, framebufferDimension);
gl.viewport(0, 0, framebufferDimension, framebufferDimension);
gl.clearColor(0, 0, 0, 0);
@@ -342,34 +233,21 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
gl.useProgram(program);
if (goog.isNull(this.locations_)) {
this.locations_ = {
aPosition: gl.getAttribLocation(program, 'aPosition'),
aTexCoord: gl.getAttribLocation(program, 'aTexCoord'),
uTileOffset: gl.getUniformLocation(program, 'uTileOffset'),
uTexture: gl.getUniformLocation(program, 'uTexture')
a_position: gl.getAttribLocation(program, 'a_position'),
a_texCoord: gl.getAttribLocation(program, 'a_texCoord'),
u_tileOffset: gl.getUniformLocation(program, 'u_tileOffset'),
u_texture: gl.getUniformLocation(program, 'u_texture')
};
}
if (goog.isNull(this.arrayBuffer_)) {
var arrayBuffer = gl.createBuffer();
gl.bindBuffer(goog.webgl.ARRAY_BUFFER, arrayBuffer);
gl.bufferData(goog.webgl.ARRAY_BUFFER, new Float32Array([
0, 0, 0, 1,
1, 0, 1, 1,
0, 1, 0, 0,
1, 1, 1, 0
]), goog.webgl.STATIC_DRAW);
this.arrayBuffer_ = arrayBuffer;
} else {
gl.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_);
}
gl.enableVertexAttribArray(this.locations_.aPosition);
mapRenderer.bindBuffer(goog.webgl.ARRAY_BUFFER, this.arrayBuffer_);
gl.enableVertexAttribArray(this.locations_.a_position);
gl.vertexAttribPointer(
this.locations_.aPosition, 2, goog.webgl.FLOAT, false, 16, 0);
gl.enableVertexAttribArray(this.locations_.aTexCoord);
this.locations_.a_position, 2, goog.webgl.FLOAT, false, 16, 0);
gl.enableVertexAttribArray(this.locations_.a_texCoord);
gl.vertexAttribPointer(
this.locations_.aTexCoord, 2, goog.webgl.FLOAT, false, 16, 8);
gl.uniform1i(this.locations_.uTexture, 0);
this.locations_.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8);
gl.uniform1i(this.locations_.u_texture, 0);
/**
* @type {Object.<number, Object.<string, ol.Tile>>}
@@ -424,7 +302,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
/** @type {Array.<number>} */
var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
goog.array.sort(zs);
var uTileOffset = goog.vec.Vec4.createFloat32();
var u_tileOffset = goog.vec.Vec4.createFloat32();
goog.array.forEach(zs, function(z) {
goog.object.forEach(tilesToDrawByZ[z], function(tile) {
var tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord);
@@ -434,8 +312,8 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
framebufferExtentSize.width - 1;
var ty = 2 * (tileExtent.minY - framebufferExtent.minY) /
framebufferExtentSize.height - 1;
goog.vec.Vec4.setFromValues(uTileOffset, sx, sy, tx, ty);
gl.uniform4fv(this.locations_.uTileOffset, uTileOffset);
goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty);
gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
mapRenderer.bindTileTexture(tile, goog.webgl.LINEAR, goog.webgl.LINEAR);
gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
}, this);
@@ -469,21 +347,22 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
tileSource.useLowResolutionTiles(z, extent, tileGrid);
this.scheduleExpireCache(frameState, tileSource);
goog.vec.Mat4.makeIdentity(this.texCoordMatrix_);
goog.vec.Mat4.translate(this.texCoordMatrix_,
var texCoordMatrix = this.texCoordMatrix;
goog.vec.Mat4.makeIdentity(texCoordMatrix);
goog.vec.Mat4.translate(texCoordMatrix,
(center.x - framebufferExtent.minX) /
(framebufferExtent.maxX - framebufferExtent.minX),
(center.y - framebufferExtent.minY) /
(framebufferExtent.maxY - framebufferExtent.minY),
0);
goog.vec.Mat4.rotateZ(this.texCoordMatrix_, view2DState.rotation);
goog.vec.Mat4.scale(this.texCoordMatrix_,
goog.vec.Mat4.rotateZ(texCoordMatrix, view2DState.rotation);
goog.vec.Mat4.scale(texCoordMatrix,
frameState.size.width * view2DState.resolution /
(framebufferExtent.maxX - framebufferExtent.minX),
frameState.size.height * view2DState.resolution /
(framebufferExtent.maxY - framebufferExtent.minY),
1);
goog.vec.Mat4.translate(this.texCoordMatrix_,
goog.vec.Mat4.translate(texCoordMatrix,
-0.5,
-0.5,
0);

191
src/ol/structs/buffer.js Normal file
View File

@@ -0,0 +1,191 @@
goog.provide('ol.structs.Buffer');
goog.require('goog.array');
goog.require('goog.webgl');
goog.require('ol.structs.IntegerSet');
/**
* @enum {number}
*/
ol.structs.BufferUsage = {
STATIC_DRAW: goog.webgl.STATIC_DRAW,
STREAM_DRAW: goog.webgl.STREAM_DRAW,
DYNAMIC_DRAW: goog.webgl.DYNAMIC_DRAW
};
/**
* @define {boolean} Replace unused entries with NaNs.
*/
ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS = goog.DEBUG;
/**
* @constructor
* @param {Array.<number>=} opt_arr Array.
* @param {number=} opt_used Used.
* @param {number=} opt_usage Usage.
*/
ol.structs.Buffer = function(opt_arr, opt_used, opt_usage) {
/**
* @private
* @type {Array.<number>}
*/
this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
/**
* @private
* @type {Array.<ol.structs.IntegerSet>}
*/
this.dirtySets_ = [];
/**
* @private
* @type {ol.structs.IntegerSet}
*/
this.freeSet_ = new ol.structs.IntegerSet();
var used = goog.isDef(opt_used) ? opt_used : this.arr_.length;
if (used < this.arr_.length) {
this.freeSet_.addRange(used, this.arr_.length);
}
if (ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS) {
var arr = this.arr_;
var n = arr.length;
var i;
for (i = used; i < n; ++i) {
arr[i] = NaN;
}
}
/**
* @private
* @type {number}
*/
this.usage_ = goog.isDef(opt_usage) ?
opt_usage : ol.structs.BufferUsage.STATIC_DRAW;
};
/**
* @param {Array.<number>} values Values.
* @return {number} Offset.
*/
ol.structs.Buffer.prototype.add = function(values) {
var size = values.length;
goog.asserts.assert(size > 0);
var offset = this.freeSet_.findRange(size);
goog.asserts.assert(offset != -1); // FIXME
this.freeSet_.removeRange(offset, offset + size);
var i;
for (i = 0; i < size; ++i) {
this.arr_[offset + i] = values[i];
}
for (i = 0; i < this.dirtySets_.length; ++i) {
this.dirtySets_[i].addRange(offset, offset + size);
}
return offset;
};
/**
* @param {ol.structs.IntegerSet} dirtySet Dirty set.
*/
ol.structs.Buffer.prototype.addDirtySet = function(dirtySet) {
goog.asserts.assert(!goog.array.contains(this.dirtySets_, dirtySet));
this.dirtySets_.push(dirtySet);
};
/**
* @param {function(this: T, number, number)} f Callback.
* @param {T=} opt_obj The object to be used as the value of 'this' within f.
* @template T
*/
ol.structs.Buffer.prototype.forEachRange = function(f, opt_obj) {
if (this.arr_.length !== 0) {
this.freeSet_.forEachRangeInverted(0, this.arr_.length, f, opt_obj);
}
};
/**
* @return {Array.<number>} Array.
*/
ol.structs.Buffer.prototype.getArray = function() {
return this.arr_;
};
/**
* @return {number} Count.
*/
ol.structs.Buffer.prototype.getCount = function() {
return this.arr_.length - this.freeSet_.getSize();
};
/**
* @return {ol.structs.IntegerSet} Free set.
*/
ol.structs.Buffer.prototype.getFreeSet = function() {
return this.freeSet_;
};
/**
* @return {number} Usage.
*/
ol.structs.Buffer.prototype.getUsage = function() {
return this.usage_;
};
/**
* @param {number} size Size.
* @param {number} offset Offset.
*/
ol.structs.Buffer.prototype.remove = function(size, offset) {
var i;
this.freeSet_.addRange(offset, offset + size);
for (i = 0; i < this.dirtySets_.length; ++i) {
this.dirtySets_[i].removeRange(offset, offset + size);
}
if (ol.BUFFER_REPLACE_UNUSED_ENTRIES_WITH_NANS) {
var arr = this.arr_;
for (i = 0; i < size; ++i) {
arr[offset + i] = NaN;
}
}
};
/**
* @param {ol.structs.IntegerSet} dirtySet Dirty set.
*/
ol.structs.Buffer.prototype.removeDirtySet = function(dirtySet) {
var removed = goog.array.remove(this.dirtySets_, dirtySet);
goog.asserts.assert(removed);
};
/**
* @param {Array.<number>} values Values.
* @param {number} offset Offset.
*/
ol.structs.Buffer.prototype.set = function(values, offset) {
var arr = this.arr_;
var n = values.length;
goog.asserts.assert(0 <= offset && offset + n <= arr.length);
var i;
for (i = 0; i < n; ++i) {
arr[offset + i] = values[i];
}
for (i = 0; i < this.dirtySets_.length; ++i) {
this.dirtySets_[i].addRange(offset, offset + n);
}
};

View File

@@ -0,0 +1,330 @@
goog.provide('ol.structs.IntegerSet');
goog.require('goog.asserts');
/**
* A set of integers represented as a set of integer ranges.
* This implementation is designed for the case when the number of distinct
* integer ranges is small.
* @constructor
* @param {Array.<number>=} opt_arr Array.
*/
ol.structs.IntegerSet = function(opt_arr) {
/**
* @private
* @type {Array.<number>}
*/
this.arr_ = goog.isDef(opt_arr) ? opt_arr : [];
if (goog.DEBUG) {
this.assertValid();
}
};
/**
* @param {number} addStart Start.
* @param {number} addStop Stop.
*/
ol.structs.IntegerSet.prototype.addRange = function(addStart, addStop) {
goog.asserts.assert(addStart <= addStop);
if (addStart == addStop) {
return;
}
var arr = this.arr_;
var n = arr.length;
var i;
for (i = 0; i < n; i += 2) {
if (addStart <= arr[i]) {
// FIXME check if splice is really needed
arr.splice(i, 0, addStart, addStop);
this.compactRanges_();
return;
}
}
arr.push(addStart, addStop);
this.compactRanges_();
};
/**
* FIXME empty description for jsdoc
*/
ol.structs.IntegerSet.prototype.assertValid = function() {
var arr = this.arr_;
var n = arr.length;
goog.asserts.assert(n % 2 === 0);
var i;
for (i = 1; i < n; ++i) {
goog.asserts.assert(arr[i] > arr[i - 1]);
}
};
/**
* FIXME empty description for jsdoc
*/
ol.structs.IntegerSet.prototype.clear = function() {
this.arr_.length = 0;
};
/**
* @private
*/
ol.structs.IntegerSet.prototype.compactRanges_ = function() {
var arr = this.arr_;
var n = arr.length;
var rangeIndex = 0;
var lastRange = null;
var i;
for (i = 0; i < n; i += 2) {
if (arr[i] == arr[i + 1]) {
// pass
} else if (rangeIndex > 0 &&
arr[rangeIndex - 2] <= arr[i] &&
arr[i] <= arr[rangeIndex - 1]) {
arr[rangeIndex - 1] = Math.max(arr[rangeIndex - 1], arr[i + 1]);
} else {
arr[rangeIndex++] = arr[i];
arr[rangeIndex++] = arr[i + 1];
}
}
arr.length = rangeIndex;
};
/**
* Finds the start of smallest range that is at least of length minSize, or -1
* if no such range exists.
* @param {number} minSize Minimum size.
* @return {number} Index.
*/
ol.structs.IntegerSet.prototype.findRange = function(minSize) {
goog.asserts.assert(minSize > 0);
var arr = this.arr_;
var n = arr.length;
var bestIndex = -1;
var bestSize, i, size;
for (i = 0; i < n; i += 2) {
size = arr[i + 1] - arr[i];
if (size == minSize) {
return arr[i];
} else if (size > minSize && (bestIndex == -1 || size < bestSize)) {
bestIndex = arr[i];
bestSize = size;
}
}
return bestIndex;
};
/**
* Calls f with each integer range.
* @param {function(this: T, number, number)} f Callback.
* @param {T=} opt_obj The object to be used as the value of 'this' within f.
* @template T
*/
ol.structs.IntegerSet.prototype.forEachRange = function(f, opt_obj) {
var arr = this.arr_;
var n = arr.length;
var i;
for (i = 0; i < n; i += 2) {
f.call(opt_obj, arr[i], arr[i + 1]);
}
};
/**
* Calls f with each integer range not in [start, stop) - 'this'.
* @param {number} start Start.
* @param {number} stop Stop.
* @param {function(this: T, number, number)} f Callback.
* @param {T=} opt_obj The object to be used as the value of 'this' within f.
* @template T
*/
ol.structs.IntegerSet.prototype.forEachRangeInverted =
function(start, stop, f, opt_obj) {
goog.asserts.assert(start < stop);
var arr = this.arr_;
var n = arr.length;
if (n === 0) {
f.call(opt_obj, start, stop);
} else {
if (start < arr[0]) {
f.call(opt_obj, start, arr[0]);
}
var i;
for (i = 1; i < n - 1; i += 2) {
f.call(opt_obj, arr[i], arr[i + 1]);
}
if (arr[n - 1] < stop) {
f.call(opt_obj, arr[n - 1], stop);
}
}
};
/**
* @return {Array.<number>} Array.
*/
ol.structs.IntegerSet.prototype.getArray = function() {
return this.arr_;
};
/**
* Returns the first element in the set, or -1 if the set is empty.
* @return {number} Start.
*/
ol.structs.IntegerSet.prototype.getFirst = function() {
return this.arr_.length === 0 ? -1 : this.arr_[0];
};
/**
* Returns the first integer after the last element in the set, or -1 if the
* set is empty.
* @return {number} Last.
*/
ol.structs.IntegerSet.prototype.getLast = function() {
var n = this.arr_.length;
return n === 0 ? -1 : this.arr_[n - 1];
};
/**
* Returns the number of integers in the set.
* @return {number} Size.
*/
ol.structs.IntegerSet.prototype.getSize = function() {
var arr = this.arr_;
var n = arr.length;
var size = 0;
var i;
for (i = 0; i < n; i += 2) {
size += arr[i + 1] - arr[i];
}
return size;
};
/**
* @param {number} start Start.
* @param {number} stop Stop.
* @return {boolean} Intersects range.
*/
ol.structs.IntegerSet.prototype.intersectsRange = function(start, stop) {
goog.asserts.assert(start <= stop);
if (start == stop) {
return false;
} else {
var arr = this.arr_;
var n = arr.length;
var i = 0;
for (i = 0; i < n; i += 2) {
if (arr[i] <= start && start < arr[i + 1] ||
arr[i] < stop && stop - 1 < arr[i + 1] ||
start < arr[i] && arr[i + 1] <= stop) {
return true;
}
}
return false;
}
};
/**
* @return {boolean} Is empty.
*/
ol.structs.IntegerSet.prototype.isEmpty = function() {
return this.arr_.length === 0;
};
/**
* @return {Array.<number>} Array.
*/
ol.structs.IntegerSet.prototype.pack = function() {
return this.arr_;
};
/**
* @param {number} removeStart Start.
* @param {number} removeStop Stop.
*/
ol.structs.IntegerSet.prototype.removeRange =
function(removeStart, removeStop) {
// FIXME this could be more efficient
goog.asserts.assert(removeStart <= removeStop);
var arr = this.arr_;
var n = arr.length;
var i;
for (i = 0; i < n; i += 2) {
if (removeStop < arr[i] || arr[i + 1] < removeStart) {
continue;
} else if (arr[i] > removeStop) {
break;
}
if (removeStart < arr[i]) {
if (removeStop == arr[i]) {
break;
} else if (removeStop < arr[i + 1]) {
arr[i] = Math.max(arr[i], removeStop);
break;
} else {
arr.splice(i, 2);
i -= 2;
n -= 2;
}
} else if (removeStart == arr[i]) {
if (removeStop < arr[i + 1]) {
arr[i] = removeStop;
break;
} else if (removeStop == arr[i + 1]) {
arr.splice(i, 2);
break;
} else {
arr.splice(i, 2);
i -= 2;
n -= 2;
}
} else {
if (removeStop < arr[i + 1]) {
arr.splice(i, 2, arr[i], removeStart, removeStop, arr[i + 1]);
break;
} else if (removeStop == arr[i + 1]) {
arr[i + 1] = removeStart;
break;
} else {
arr[i + 1] = removeStart;
}
}
}
this.compactRanges_();
};
if (goog.DEBUG) {
/**
* @return {string} String.
*/
ol.structs.IntegerSet.prototype.toString = function() {
var arr = this.arr_;
var n = arr.length;
var result = new Array(n / 2);
var resultIndex = 0;
var i;
for (i = 0; i < n; i += 2) {
result[resultIndex++] = arr[i] + '-' + arr[i + 1];
}
return result.join(', ');
};
}

View File

@@ -522,7 +522,8 @@
if (equal) {
var j = 0;
for (j = 0; j < other.length; ++j) {
if (this.obj[j] !== other[j]) {
if (this.obj[j] !== other[j] &&
!(isNaN(this.obj[j]) && isNaN(other[j]))) {
equal = false;
break;
}

View File

@@ -64,6 +64,46 @@ describe('ol.math.csch', function() {
});
describe('ol.math.roundUpToPowerOfTwo', function() {
it('raises an exception when x is negative', function() {
expect(function() {
ol.math.roundUpToPowerOfTwo(-1);
}).to.throwException();
});
it('raises an exception when x is zero', function() {
expect(function() {
ol.math.roundUpToPowerOfTwo(0);
}).to.throwException();
});
it('returns the expected value for simple powers of two', function() {
expect(ol.math.roundUpToPowerOfTwo(1)).to.be(1);
expect(ol.math.roundUpToPowerOfTwo(2)).to.be(2);
expect(ol.math.roundUpToPowerOfTwo(4)).to.be(4);
expect(ol.math.roundUpToPowerOfTwo(8)).to.be(8);
expect(ol.math.roundUpToPowerOfTwo(16)).to.be(16);
expect(ol.math.roundUpToPowerOfTwo(32)).to.be(32);
expect(ol.math.roundUpToPowerOfTwo(64)).to.be(64);
expect(ol.math.roundUpToPowerOfTwo(128)).to.be(128);
expect(ol.math.roundUpToPowerOfTwo(256)).to.be(256);
});
it('returns the expected value for simple powers of ten', function() {
expect(ol.math.roundUpToPowerOfTwo(1)).to.be(1);
expect(ol.math.roundUpToPowerOfTwo(10)).to.be(16);
expect(ol.math.roundUpToPowerOfTwo(100)).to.be(128);
expect(ol.math.roundUpToPowerOfTwo(1000)).to.be(1024);
expect(ol.math.roundUpToPowerOfTwo(10000)).to.be(16384);
expect(ol.math.roundUpToPowerOfTwo(100000)).to.be(131072);
expect(ol.math.roundUpToPowerOfTwo(1000000)).to.be(1048576);
expect(ol.math.roundUpToPowerOfTwo(10000000)).to.be(16777216);
});
});
describe('ol.math.sech', function() {
it('returns the correct value at -Infinity', function() {

View File

@@ -0,0 +1,262 @@
goog.provide('ol.test.structs.Buffer');
describe('ol.structs.Buffer', function() {
describe('constructor', function() {
describe('without an argument', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer();
});
it('constructs an empty instance', function() {
expect(b.getArray()).to.be.empty();
expect(b.getCount()).to.be(0);
});
});
describe('with a single array argument', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer([0, 1, 2, 3]);
});
it('constructs a populated instance', function() {
expect(b.getArray()).to.equalArray([0, 1, 2, 3]);
});
});
});
describe('with an empty instance', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer();
});
describe('forEachRange', function() {
it('does not call the callback', function() {
var callback = sinon.spy();
b.forEachRange(callback);
expect(callback).not.to.be.called();
});
});
describe('getArray', function() {
it('returns an empty array', function() {
expect(b.getArray()).to.be.empty();
});
});
describe('getCount', function() {
it('returns 0', function() {
expect(b.getCount()).to.be(0);
});
});
});
describe('with an empty instance with spare capacity', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer(new Array(4), 0);
});
describe('add', function() {
it('allows elements to be added', function() {
expect(b.add([0, 1, 2, 3])).to.be(0);
expect(b.getArray()).to.equalArray([0, 1, 2, 3]);
});
});
describe('forEachRange', function() {
it('does not call the callback', function() {
var callback = sinon.spy();
b.forEachRange(callback);
expect(callback).not.to.be.called();
});
});
describe('getCount', function() {
it('returns 0', function() {
expect(b.getCount()).to.be(0);
});
});
});
describe('with an instance with no spare capacity', function() {
var b;
beforeEach(function() {
b = new ol.structs.Buffer([0, 1, 2, 3]);
});
describe('add', function() {
it('throws an exception', function() {
expect(function() {
b.add([4, 5]);
}).to.throwException();
});
});
describe('forEachRange', function() {
it('calls the callback', function() {
var callback = sinon.spy();
b.forEachRange(callback);
expect(callback.calledOnce).to.be(true);
expect(callback.args[0]).to.equalArray([0, 4]);
});
});
describe('getCount', function() {
it('returns the expected value', function() {
expect(b.getCount()).to.be(4);
});
});
describe('remove', function() {
it('allows items to be removes', function() {
expect(function() {
b.remove(4, 2);
}).to.not.throwException();
});
});
describe('set', function() {
it('updates the items', function() {
b.set([5, 6], 2);
expect(b.getArray()).to.equalArray([0, 1, 5, 6]);
});
it('marks the set items as dirty', function() {
var dirtySet = new ol.structs.IntegerSet();
b.addDirtySet(dirtySet);
expect(dirtySet.isEmpty()).to.be(true);
b.set([5, 6], 2);
expect(dirtySet.isEmpty()).to.be(false);
expect(dirtySet.getArray()).to.equalArray([2, 4]);
});
});
});
describe('with an instance with spare capacity', function() {
var b;
beforeEach(function() {
var arr = [0, 1, 2, 3];
arr.length = 8;
b = new ol.structs.Buffer(arr, 4);
});
describe('add', function() {
it('allows more items to be added', function() {
expect(b.add([4, 5, 6, 7])).to.be(4);
expect(b.getArray()).to.equalArray([0, 1, 2, 3, 4, 5, 6, 7]);
});
});
describe('forEachRange', function() {
it('calls the callback with the expected values', function() {
var callback = sinon.spy();
b.forEachRange(callback);
expect(callback.calledOnce).to.be(true);
expect(callback.args[0]).to.equalArray([0, 4]);
});
});
describe('getCount', function() {
it('returns the expected value', function() {
expect(b.getCount()).to.be(4);
});
});
describe('getFreeSet', function() {
it('returns the expected set', function() {
var freeSet = b.getFreeSet();
expect(freeSet.isEmpty()).to.be(false);
expect(freeSet.getArray()).to.equalArray([4, 8]);
});
});
});
describe('usage tests', function() {
it('allows multiple adds and removes', function() {
var b = new ol.structs.Buffer(new Array(8), 0);
expect(b.add([0, 1])).to.be(0);
expect(b.getArray()).to.equalArray([0, 1, NaN, NaN, NaN, NaN, NaN, NaN]);
expect(b.getCount()).to.be(2);
expect(b.add([2, 3, 4, 5])).to.be(2);
expect(b.getArray()).to.equalArray([0, 1, 2, 3, 4, 5, NaN, NaN]);
expect(b.getCount()).to.be(6);
expect(b.add([6, 7])).to.be(6);
expect(b.getArray()).to.equalArray([0, 1, 2, 3, 4, 5, 6, 7]);
expect(b.getCount()).to.be(8);
b.remove(2, 2);
expect(b.getArray()).to.equalArray([0, 1, NaN, NaN, 4, 5, 6, 7]);
expect(b.getCount()).to.be(6);
expect(b.add([8, 9])).to.be(2);
expect(b.getArray()).to.equalArray([0, 1, 8, 9, 4, 5, 6, 7]);
expect(b.getCount()).to.be(8);
b.remove(1, 1);
expect(b.getArray()).to.equalArray([0, NaN, 8, 9, 4, 5, 6, 7]);
expect(b.getCount()).to.be(7);
b.remove(4, 4);
expect(b.getArray()).to.equalArray([0, NaN, 8, 9, NaN, NaN, NaN, NaN]);
expect(b.getCount()).to.be(3);
expect(b.add([10, 11, 12])).to.be(4);
expect(b.getArray()).to.equalArray([0, NaN, 8, 9, 10, 11, 12, NaN]);
expect(b.getCount()).to.be(6);
expect(b.add([13])).to.be(1);
expect(b.getArray()).to.equalArray([0, 13, 8, 9, 10, 11, 12, NaN]);
expect(b.getCount()).to.be(7);
});
});
});
goog.require('ol.structs.Buffer');
goog.require('ol.structs.IntegerSet');

View File

@@ -0,0 +1,619 @@
goog.provide('ol.test.structs.IntegerSet');
describe('ol.structs.IntegerSet', function() {
describe('constructor', function() {
describe('without an argument', function() {
it('constructs an empty instance', function() {
var is = new ol.structs.IntegerSet();
expect(is).to.be.an(ol.structs.IntegerSet);
expect(is.getArray()).to.be.empty();
});
});
describe('with an argument', function() {
it('constructs with a valid array', function() {
var is = new ol.structs.IntegerSet([0, 2, 4, 6]);
expect(is).to.be.an(ol.structs.IntegerSet);
expect(is.getArray()).to.equalArray([0, 2, 4, 6]);
});
it('throws an exception with an odd number of elements', function() {
expect(function() {
var is = new ol.structs.IntegerSet([0, 2, 4]);
}).to.throwException();
});
it('throws an exception with out-of-order elements', function() {
expect(function() {
var is = new ol.structs.IntegerSet([0, 2, 2, 4]);
}).to.throwException();
});
});
});
describe('with an empty instance', function() {
var is;
beforeEach(function() {
is = new ol.structs.IntegerSet();
});
describe('addRange', function() {
it('creates a new element', function() {
is.addRange(0, 2);
expect(is.getArray()).to.equalArray([0, 2]);
});
});
describe('findRange', function() {
it('returns -1', function() {
expect(is.findRange(2)).to.be(-1);
});
});
describe('forEachRange', function() {
it('does not call the callback', function() {
var callback = sinon.spy();
is.forEachRange(callback);
expect(callback).to.not.be.called();
});
});
describe('forEachRangeInverted', function() {
it('does call the callback', function() {
var callback = sinon.spy();
is.forEachRangeInverted(0, 8, callback);
expect(callback.calledOnce).to.be(true);
expect(callback.args[0]).to.equalArray([0, 8]);
});
});
describe('getFirst', function() {
it('returns -1', function() {
expect(is.getFirst()).to.be(-1);
});
});
describe('getLast', function() {
it('returns -1', function() {
expect(is.getLast()).to.be(-1);
});
});
describe('getSize', function() {
it('returns 0', function() {
expect(is.getSize()).to.be(0);
});
});
describe('intersectsRange', function() {
it('returns false', function() {
expect(is.intersectsRange(0, 0)).to.be(false);
});
});
describe('isEmpty', function() {
it('returns true', function() {
expect(is.isEmpty()).to.be(true);
});
});
describe('toString', function() {
it('returns an empty string', function() {
expect(is.toString()).to.be.empty();
});
});
});
describe('with a populated instance', function() {
var is;
beforeEach(function() {
is = new ol.structs.IntegerSet([4, 6, 8, 10, 12, 14]);
});
describe('addRange', function() {
it('inserts before the first element', function() {
is.addRange(0, 2);
expect(is.getArray()).to.equalArray([0, 2, 4, 6, 8, 10, 12, 14]);
});
it('extends the first element to the left', function() {
is.addRange(0, 4);
expect(is.getArray()).to.equalArray([0, 6, 8, 10, 12, 14]);
});
it('extends the first element to the right', function() {
is.addRange(6, 7);
expect(is.getArray()).to.equalArray([4, 7, 8, 10, 12, 14]);
});
it('merges the first two elements', function() {
is.addRange(6, 8);
expect(is.getArray()).to.equalArray([4, 10, 12, 14]);
});
it('extends middle elements to the left', function() {
is.addRange(7, 8);
expect(is.getArray()).to.equalArray([4, 6, 7, 10, 12, 14]);
});
it('extends middle elements to the right', function() {
is.addRange(10, 11);
expect(is.getArray()).to.equalArray([4, 6, 8, 11, 12, 14]);
});
it('merges the last two elements', function() {
is.addRange(10, 12);
expect(is.getArray()).to.equalArray([4, 6, 8, 14]);
});
it('extends the last element to the left', function() {
is.addRange(11, 12);
expect(is.getArray()).to.equalArray([4, 6, 8, 10, 11, 14]);
});
it('extends the last element to the right', function() {
is.addRange(14, 15);
expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 15]);
});
it('inserts after the last element', function() {
is.addRange(16, 18);
expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 14, 16, 18]);
});
});
describe('clear', function() {
it('clears the instance', function() {
is.clear();
expect(is.getArray()).to.be.empty();
});
});
describe('findRange', function() {
it('throws an exception when passed a negative size', function() {
expect(function() {
is.findRange(-1);
}).to.throwException();
});
it('throws an exception when passed a zero size', function() {
expect(function() {
is.findRange(0);
}).to.throwException();
});
it('finds the first range of size 1', function() {
expect(is.findRange(1)).to.be(4);
});
it('finds the first range of size 2', function() {
expect(is.findRange(2)).to.be(4);
});
it('returns -1 when no range can be found', function() {
expect(is.findRange(3)).to.be(-1);
});
});
describe('forEachRange', function() {
it('calls the callback', function() {
var callback = sinon.spy();
is.forEachRange(callback);
expect(callback).to.be.called();
expect(callback.calledThrice).to.be(true);
expect(callback.args[0]).to.equalArray([4, 6]);
expect(callback.args[1]).to.equalArray([8, 10]);
expect(callback.args[2]).to.equalArray([12, 14]);
});
});
describe('forEachRangeInverted', function() {
it('does call the callback', function() {
var callback = sinon.spy();
is.forEachRangeInverted(0, 16, callback);
expect(callback.callCount).to.be(4);
expect(callback.args[0]).to.equalArray([0, 4]);
expect(callback.args[1]).to.equalArray([6, 8]);
expect(callback.args[2]).to.equalArray([10, 12]);
expect(callback.args[3]).to.equalArray([14, 16]);
});
});
describe('getFirst', function() {
it('returns the expected value', function() {
expect(is.getFirst()).to.be(4);
});
});
describe('getLast', function() {
it('returns the expected value', function() {
expect(is.getLast()).to.be(14);
});
});
describe('getSize', function() {
it('returns the expected value', function() {
expect(is.getSize()).to.be(6);
});
});
describe('intersectsRange', function() {
it('returns the expected value for small ranges', function() {
expect(is.intersectsRange(1, 3)).to.be(false);
expect(is.intersectsRange(2, 4)).to.be(false);
expect(is.intersectsRange(3, 5)).to.be(true);
expect(is.intersectsRange(4, 6)).to.be(true);
expect(is.intersectsRange(5, 7)).to.be(true);
expect(is.intersectsRange(6, 8)).to.be(false);
expect(is.intersectsRange(7, 9)).to.be(true);
expect(is.intersectsRange(8, 10)).to.be(true);
expect(is.intersectsRange(9, 11)).to.be(true);
expect(is.intersectsRange(10, 12)).to.be(false);
expect(is.intersectsRange(11, 13)).to.be(true);
expect(is.intersectsRange(12, 14)).to.be(true);
expect(is.intersectsRange(13, 15)).to.be(true);
expect(is.intersectsRange(14, 16)).to.be(false);
expect(is.intersectsRange(15, 17)).to.be(false);
});
it('returns the expected value for large ranges', function() {
expect(is.intersectsRange(-3, 1)).to.be(false);
expect(is.intersectsRange(1, 5)).to.be(true);
expect(is.intersectsRange(1, 9)).to.be(true);
expect(is.intersectsRange(1, 13)).to.be(true);
expect(is.intersectsRange(1, 17)).to.be(true);
expect(is.intersectsRange(5, 9)).to.be(true);
expect(is.intersectsRange(5, 13)).to.be(true);
expect(is.intersectsRange(5, 17)).to.be(true);
expect(is.intersectsRange(9, 13)).to.be(true);
expect(is.intersectsRange(9, 17)).to.be(true);
expect(is.intersectsRange(13, 17)).to.be(true);
expect(is.intersectsRange(17, 21)).to.be(false);
});
});
describe('isEmpty', function() {
it('returns false', function() {
expect(is.isEmpty()).to.be(false);
});
});
describe('removeRange', function() {
it('removes the first part of the first element', function() {
is.removeRange(4, 5);
expect(is.getArray()).to.equalArray([5, 6, 8, 10, 12, 14]);
});
it('removes the last part of the first element', function() {
is.removeRange(5, 6);
expect(is.getArray()).to.equalArray([4, 5, 8, 10, 12, 14]);
});
it('removes the first element', function() {
is.removeRange(4, 6);
expect(is.getArray()).to.equalArray([8, 10, 12, 14]);
});
it('removes the first part of a middle element', function() {
is.removeRange(8, 9);
expect(is.getArray()).to.equalArray([4, 6, 9, 10, 12, 14]);
});
it('removes the last part of a middle element', function() {
is.removeRange(9, 10);
expect(is.getArray()).to.equalArray([4, 6, 8, 9, 12, 14]);
});
it('removes a middle element', function() {
is.removeRange(8, 10);
expect(is.getArray()).to.equalArray([4, 6, 12, 14]);
});
it('removes the first part of the last element', function() {
is.removeRange(12, 13);
expect(is.getArray()).to.equalArray([4, 6, 8, 10, 13, 14]);
});
it('removes the last part of the last element', function() {
is.removeRange(13, 14);
expect(is.getArray()).to.equalArray([4, 6, 8, 10, 12, 13]);
});
it('removes the last element', function() {
is.removeRange(12, 14);
expect(is.getArray()).to.equalArray([4, 6, 8, 10]);
});
it('can remove multiple ranges near the start', function() {
is.removeRange(3, 11);
expect(is.getArray()).to.equalArray([12, 14]);
});
it('can remove multiple ranges near the start', function() {
is.removeRange(7, 15);
expect(is.getArray()).to.equalArray([4, 6]);
});
it('throws an exception when passed an invalid range', function() {
expect(function() {
is.removeRange(2, 0);
}).to.throwException();
});
});
describe('toString', function() {
it('returns the expected value', function() {
expect(is.toString()).to.be('4-6, 8-10, 12-14');
});
});
});
describe('with fragmentation', function() {
var is;
beforeEach(function() {
is = new ol.structs.IntegerSet([0, 1, 2, 4, 5, 8, 9, 12, 13, 15, 16, 17]);
});
describe('findRange', function() {
it('finds the first range of size 1', function() {
expect(is.findRange(1)).to.be(0);
});
it('finds the first range of size 2', function() {
expect(is.findRange(2)).to.be(2);
});
it('finds the first range of size 3', function() {
expect(is.findRange(3)).to.be(5);
});
it('returns -1 when no range can be found', function() {
expect(is.findRange(4)).to.be(-1);
});
});
describe('getFirst', function() {
it('returns the expected value', function() {
expect(is.getFirst()).to.be(0);
});
});
describe('getLast', function() {
it('returns the expected value', function() {
expect(is.getLast()).to.be(17);
});
});
describe('getSize', function() {
it('returns the expected value', function() {
expect(is.getSize()).to.be(12);
});
});
describe('removeRange', function() {
it('removing an empty range has no effect', function() {
is.removeRange(0, 0);
expect(is.getArray()).to.equalArray(
[0, 1, 2, 4, 5, 8, 9, 12, 13, 15, 16, 17]);
});
it('can remove elements from the middle of range', function() {
is.removeRange(6, 7);
expect(is.getArray()).to.equalArray(
[0, 1, 2, 4, 5, 6, 7, 8, 9, 12, 13, 15, 16, 17]);
});
it('can remove multiple ranges', function() {
is.removeRange(2, 12);
expect(is.getArray()).to.equalArray([0, 1, 13, 15, 16, 17]);
});
it('can remove multiple ranges and reduce others', function() {
is.removeRange(0, 10);
expect(is.getArray()).to.equalArray([10, 12, 13, 15, 16, 17]);
});
it('can remove all ranges', function() {
is.removeRange(0, 18);
expect(is.getArray()).to.equalArray([]);
});
});
describe('toString', function() {
it('returns the expected value', function() {
expect(is.toString()).to.be('0-1, 2-4, 5-8, 9-12, 13-15, 16-17');
});
});
});
describe('compared to a slow reference implementation', function() {
var SimpleIntegerSet = function() {
this.integers_ = {};
};
SimpleIntegerSet.prototype.addRange = function(addStart, addStop) {
var i;
for (i = addStart; i < addStop; ++i) {
this.integers_[i.toString()] = true;
}
};
SimpleIntegerSet.prototype.clear = function() {
this.integers_ = {};
};
SimpleIntegerSet.prototype.getArray = function() {
var integers = goog.array.map(
goog.object.getKeys(this.integers_), Number);
goog.array.sort(integers);
var arr = [];
var start = -1, stop;
var i;
for (i = 0; i < integers.length; ++i) {
if (start == -1) {
start = stop = integers[i];
} else if (integers[i] == stop + 1) {
++stop;
} else {
arr.push(start, stop + 1);
start = stop = integers[i];
}
}
if (start != -1) {
arr.push(start, stop + 1);
}
return arr;
};
SimpleIntegerSet.prototype.removeRange = function(removeStart, removeStop) {
var i;
for (i = removeStart; i < removeStop; ++i) {
delete this.integers_[i.toString()];
}
};
var is, sis;
beforeEach(function() {
is = new ol.structs.IntegerSet();
sis = new SimpleIntegerSet();
});
it('behaves identically with random adds', function() {
var addStart, addStop, i;
for (i = 0; i < 64; ++i) {
addStart = goog.math.randomInt(128);
addStop = addStart + goog.math.randomInt(16);
is.addRange(addStart, addStop);
sis.addRange(addStart, addStop);
expect(is.getArray()).to.equalArray(sis.getArray());
}
});
it('behaves identically with random removes', function() {
is.addRange(0, 128);
sis.addRange(0, 128);
var i, removeStart, removeStop;
for (i = 0; i < 64; ++i) {
removeStart = goog.math.randomInt(128);
removeStop = removeStart + goog.math.randomInt(16);
is.removeRange(removeStart, removeStop);
sis.removeRange(removeStart, removeStop);
expect(is.getArray()).to.equalArray(sis.getArray());
}
});
it('behaves identically with random adds and removes', function() {
var i, start, stop;
for (i = 0; i < 64; ++i) {
start = goog.math.randomInt(128);
stop = start + goog.math.randomInt(16);
if (Math.random() < 0.5) {
is.addRange(start, stop);
sis.addRange(start, stop);
} else {
is.removeRange(start, stop);
sis.removeRange(start, stop);
}
expect(is.getArray()).to.equalArray(sis.getArray());
}
});
it('behaves identically with random adds, removes, and clears', function() {
var i, p, start, stop;
for (i = 0; i < 64; ++i) {
start = goog.math.randomInt(128);
stop = start + goog.math.randomInt(16);
p = Math.random();
if (p < 0.45) {
is.addRange(start, stop);
sis.addRange(start, stop);
} else if (p < 0.9) {
is.removeRange(start, stop);
sis.removeRange(start, stop);
} else {
is.clear();
sis.clear();
}
expect(is.getArray()).to.equalArray(sis.getArray());
}
});
});
});
goog.require('goog.array');
goog.require('goog.object');
goog.require('ol.structs.IntegerSet');