Merge pull request #8977 from jahow/add-webgl-rendering-tests

Add WebGL rendering tests & improve the WebGLHelper API
This commit is contained in:
Tim Schaub
2018-11-20 03:59:06 -08:00
committed by GitHub
11 changed files with 210 additions and 133 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -0,0 +1,44 @@
import Map from '../../../src/ol/Map.js';
import View from '../../../src/ol/View.js';
import TileLayer from '../../../src/ol/layer/Tile.js';
import XYZ from '../../../src/ol/source/XYZ';
import {Heatmap as HeatmapLayer} from '../../../src/ol/layer';
import VectorSource from '../../../src/ol/source/Vector';
import KML from '../../../src/ol/format/KML';
const vector = new HeatmapLayer({
source: new VectorSource({
url: '/data/2012_Earthquakes_Mag5.kml',
format: new KML({
extractStyles: false
})
}),
blur: 3,
radius: 3
});
vector.getSource().on('addfeature', function(event) {
const name = event.feature.get('name');
const magnitude = parseFloat(name.substr(2));
event.feature.set('weight', magnitude - 5);
});
const raster = new TileLayer({
source: new XYZ({
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
transition: 0
})
});
new Map({
layers: [raster, vector],
target: 'map',
view: new View({
center: [0, 0],
zoom: 0
})
});
render({
message: 'Heatmap layer renders properly using webgl'
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -0,0 +1,47 @@
import Map from '../../../src/ol/Map.js';
import View from '../../../src/ol/View.js';
import TileLayer from '../../../src/ol/layer/Tile.js';
import XYZ from '../../../src/ol/source/XYZ';
import {Vector as VectorLayer} from '../../../src/ol/layer';
import VectorSource from '../../../src/ol/source/Vector';
import KML from '../../../src/ol/format/KML';
import WebGLPointsLayerRenderer from '../../../src/ol/renderer/webgl/PointsLayer';
class CustomLayer extends VectorLayer {
createRenderer() {
return new WebGLPointsLayerRenderer(this, {
sizeCallback: function() {
return 4;
}
});
}
}
const vector = new CustomLayer({
source: new VectorSource({
url: '/data/2012_Earthquakes_Mag5.kml',
format: new KML({
extractStyles: false
})
})
});
const raster = new TileLayer({
source: new XYZ({
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
transition: 0
})
});
new Map({
layers: [raster, vector],
target: 'map',
view: new View({
center: [15180597.9736, 2700366.3807],
zoom: 2
})
});
render({
message: 'Points are rendered using webgl as 4px pixel squares'
});

View File

@@ -0,0 +1 @@
../../examples/data/kml/2012_Earthquakes_Mag5.kml

View File

@@ -356,7 +356,7 @@ if (require.main === module) {
option('puppeteer-args', {
describe: 'Additional args for Puppeteer',
type: 'array',
default: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : []
default: process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-gpu-blacklist'] : []
}).
parse();

View File

@@ -5,8 +5,6 @@ import LayerRenderer from '../Layer';
import WebGLArrayBuffer from '../../webgl/Buffer';
import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl';
import WebGLHelper, {DefaultAttrib, DefaultUniform} from '../../webgl/Helper';
import WebGLVertex from '../../webgl/Vertex';
import WebGLFragment from '../../webgl/Fragment';
import GeometryType from '../../geom/GeometryType';
const VERTEX_SHADER = `
@@ -136,7 +134,7 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
const options = opt_options || {};
this.context_ = new WebGLHelper({
this.helper_ = new WebGLHelper({
postProcesses: options.postProcesses,
uniforms: options.uniforms
});
@@ -146,10 +144,12 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
this.verticesBuffer_ = new WebGLArrayBuffer([], DYNAMIC_DRAW);
this.indicesBuffer_ = new WebGLArrayBuffer([], DYNAMIC_DRAW);
const vertexShader = new WebGLVertex(options.vertexShader || VERTEX_SHADER);
const fragmentShader = new WebGLFragment(options.fragmentShader || FRAGMENT_SHADER);
this.program_ = this.context_.getProgram(fragmentShader, vertexShader);
this.context_.useProgram(this.program_);
this.program_ = this.helper_.getProgram(
options.fragmentShader || FRAGMENT_SHADER,
options.vertexShader || VERTEX_SHADER
);
this.helper_.useProgram(this.program_);
this.sizeCallback_ = options.sizeCallback || function(feature) {
return 1;
@@ -171,10 +171,10 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
* @inheritDoc
*/
renderFrame(frameState, layerState) {
this.context_.setUniformFloatValue(DefaultUniform.OPACITY, layerState.opacity);
this.context_.drawElements(0, this.indicesBuffer_.getArray().length);
this.context_.finalizeDraw(frameState);
return this.context_.getCanvas();
this.helper_.setUniformFloatValue(DefaultUniform.OPACITY, layerState.opacity);
this.helper_.drawElements(0, this.indicesBuffer_.getArray().length);
this.helper_.finalizeDraw(frameState);
return this.helper_.getCanvas();
}
/**
@@ -184,7 +184,7 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
const vectorLayer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer());
const vectorSource = /** @type {import("../../source/Vector.js").default} */ (vectorLayer.getSource());
this.context_.prepareDraw(frameState);
this.helper_.prepareDraw(frameState);
if (this.sourceRevision_ < vectorSource.getRevision()) {
this.sourceRevision_ = vectorSource.getRevision();
@@ -219,16 +219,25 @@ class WebGLPointsLayerRenderer extends LayerRenderer {
}
// write new data
this.context_.bindBuffer(ARRAY_BUFFER, this.verticesBuffer_);
this.context_.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
this.helper_.bindBuffer(ARRAY_BUFFER, this.verticesBuffer_);
this.helper_.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
const bytesPerFloat = Float32Array.BYTES_PER_ELEMENT;
this.context_.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * 6, 0);
this.context_.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * 6, bytesPerFloat * 2);
this.context_.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * 6, bytesPerFloat * 4);
this.helper_.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * 6, 0);
this.helper_.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * 6, bytesPerFloat * 2);
this.helper_.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * 6, bytesPerFloat * 4);
return true;
}
/**
* Will return the last shader compilation errors. If no error happened, will return null;
* @return {string|null} Errors, or null if last compilation was successful
* @api
*/
getShaderCompileErrors() {
return this.helper_.getShaderCompileErrors();
}
}
export default WebGLPointsLayerRenderer;

View File

@@ -1,26 +0,0 @@
/**
* @module ol/webgl/Fragment
*/
import {FRAGMENT_SHADER} from '../webgl.js';
import WebGLShader from '../webgl/Shader.js';
class WebGLFragment extends WebGLShader {
/**
* @param {string} source Source.
*/
constructor(source) {
super(source);
}
/**
* @inheritDoc
*/
getType() {
return FRAGMENT_SHADER;
}
}
export default WebGLFragment;

View File

@@ -26,6 +26,15 @@ import WebGLPostProcessingPass from './PostProcessingPass';
* @property {WebGLBuffer} buffer
*/
/**
* Shader types, either `FRAGMENT_SHADER` or `VERTEX_SHADER`
* @enum {number}
*/
export const ShaderType = {
FRAGMENT_SHADER: 0x8B30,
VERTEX_SHADER: 0x8B31
};
/**
* Uniform names used in the default shaders.
* @const
@@ -222,15 +231,15 @@ class WebGLHelper extends Disposable {
/**
* @private
* @type {!Object<string, WebGLShader>}
* @type {!Array<WebGLShader>}
*/
this.shaderCache_ = {};
this.shaderCache_ = [];
/**
* @private
* @type {!Object<string, WebGLProgram>}
* @type {!Array<WebGLProgram>}
*/
this.programCache_ = {};
this.programCache_ = [];
/**
* @private
@@ -319,6 +328,12 @@ class WebGLHelper extends Disposable {
uniforms: options.uniforms
});
}) : [new WebGLPostProcessingPass({webGlContext: gl})];
/**
* @type {string|null}
* @private
*/
this.shaderCompileErrors_ = null;
}
/**
@@ -552,28 +567,6 @@ class WebGLHelper extends Disposable {
}.bind(this));
}
/**
* Get shader from the cache if it's in the cache. Otherwise, create
* the WebGL shader, compile it, and add entry to cache.
* TODO: make compilation errors show up
* @param {import("./Shader.js").default} shaderObject Shader object.
* @return {WebGLShader} Shader.
* @api
*/
getShader(shaderObject) {
const shaderKey = getUid(shaderObject);
if (shaderKey in this.shaderCache_) {
return this.shaderCache_[shaderKey];
} else {
const gl = this.getGL();
const shader = gl.createShader(shaderObject.getType());
gl.shaderSource(shader, shaderObject.getSource());
gl.compileShader(shader);
this.shaderCache_[shaderKey] = shader;
return shader;
}
}
/**
* Use a program. If the program is already in use, this will return `false`.
* @param {WebGLProgram} program Program.
@@ -594,27 +587,62 @@ class WebGLHelper extends Disposable {
}
/**
* Get the program from the cache if it's in the cache. Otherwise create
* the WebGL program, attach the shaders to it, and add an entry to the
* cache.
* @param {import("./Fragment.js").default} fragmentShaderObject Fragment shader.
* @param {import("./Vertex.js").default} vertexShaderObject Vertex shader.
* @return {WebGLProgram} Program.
* Will attempt to compile a vertex or fragment shader based on source
* On error, the shader will be returned but
* `gl.getShaderParameter(shader, gl.COMPILE_STATUS)` will return `true`
* Use `gl.getShaderInfoLog(shader)` to have details
* @param {string} source Shader source
* @param {ShaderType} type VERTEX_SHADER or FRAGMENT_SHADER
* @return {WebGLShader} Shader object
*/
compileShader(source, type) {
const gl = this.getGL();
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
this.shaderCache_.push(shader);
return shader;
}
/**
* Create a program for a vertex and fragment shader. The shaders compilation may have failed:
* use `WebGLHelper.getShaderCompileErrors()`to have details if any.
* @param {string} fragmentShaderSource Fragment shader source.
* @param {string} vertexShaderSource Vertex shader source.
* @return {WebGLProgram} Program
* @api
*/
getProgram(fragmentShaderObject, vertexShaderObject) {
const programKey = getUid(fragmentShaderObject) + '/' + getUid(vertexShaderObject);
if (programKey in this.programCache_) {
return this.programCache_[programKey];
} else {
const gl = this.getGL();
const program = gl.createProgram();
gl.attachShader(program, this.getShader(fragmentShaderObject));
gl.attachShader(program, this.getShader(vertexShaderObject));
gl.linkProgram(program);
this.programCache_[programKey] = program;
return program;
getProgram(fragmentShaderSource, vertexShaderSource) {
const gl = this.getGL();
const fragmentShader = this.compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER);
const vertexShader = this.compileShader(vertexShaderSource, gl.VERTEX_SHADER);
this.shaderCompileErrors_ = null;
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
this.shaderCompileErrors_ =
`Fragment shader compilation failed:\n${gl.getShaderInfoLog(fragmentShader)}`;
}
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
this.shaderCompileErrors_ = (this.shaderCompileErrors_ || '') +
`Vertex shader compilation failed:\n${gl.getShaderInfoLog(vertexShader)}`;
}
const program = gl.createProgram();
gl.attachShader(program, fragmentShader);
gl.attachShader(program, vertexShader);
gl.linkProgram(program);
this.programCache_.push(program);
return program;
}
/**
* Will return the last shader compilation errors. If no error happened, will return null;
* @return {string|null} Errors description, or null if last compilation was successful
* @api
*/
getShaderCompileErrors() {
return this.shaderCompileErrors_;
}
/**
@@ -624,7 +652,7 @@ class WebGLHelper extends Disposable {
* @api
*/
getUniformLocation(name) {
if (!this.uniformLocations_[name]) {
if (this.uniformLocations_[name] === undefined) {
this.uniformLocations_[name] = this.getGL().getUniformLocation(this.currentProgram_, name);
}
return this.uniformLocations_[name];
@@ -637,7 +665,7 @@ class WebGLHelper extends Disposable {
* @api
*/
getAttributeLocation(name) {
if (!this.attribLocations_[name]) {
if (this.attribLocations_[name] === undefined) {
this.attribLocations_[name] = this.getGL().getAttribLocation(this.currentProgram_, name);
}
return this.attribLocations_[name];

View File

@@ -1,26 +0,0 @@
/**
* @module ol/webgl/Vertex
*/
import {VERTEX_SHADER} from '../webgl.js';
import WebGLShader from '../webgl/Shader.js';
class WebGLVertex extends WebGLShader {
/**
* @param {string} source Source.
*/
constructor(source) {
super(source);
}
/**
* @inheritDoc
*/
getType() {
return VERTEX_SHADER;
}
}
export default WebGLVertex;

View File

@@ -1,6 +1,4 @@
import WebGLHelper from '../../../../src/ol/webgl/Helper';
import WebGLVertex from '../../../../src/ol/webgl/Vertex';
import WebGLFragment from '../../../../src/ol/webgl/Fragment';
const VERTEX_SHADER = `
@@ -20,11 +18,15 @@ const VERTEX_SHADER = `
const INVALID_VERTEX_SHADER = `
precision mediump float;
attribute float a_test;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
uniform mat4 u_offsetRotateMatrix;
bla
uniform float u_test;
void main(void) {
gl_Position = vec4(1, 1, 1, 0);
gl_Position = vec4(u_test, a_test, 0.0, 1.0);
}`;
const FRAGMENT_SHADER = `
@@ -96,9 +98,7 @@ describe('ol.webgl.WebGLHelper', function() {
u_test3: document.createElement('canvas')
}
});
const vertexShader = new WebGLVertex(VERTEX_SHADER);
const fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
h.useProgram(h.getProgram(fragmentShader, vertexShader));
h.useProgram(h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER));
h.prepareDraw({
pixelRatio: 2,
size: [50, 80],
@@ -133,9 +133,7 @@ describe('ol.webgl.WebGLHelper', function() {
beforeEach(function() {
h = new WebGLHelper();
const vertexShader = new WebGLVertex(VERTEX_SHADER);
const fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
p = h.getProgram(fragmentShader, vertexShader);
p = h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER);
h.useProgram(p);
});
@@ -143,6 +141,10 @@ describe('ol.webgl.WebGLHelper', function() {
expect(h.currentProgram_).to.eql(p);
});
it('has no shader compilation error', function() {
expect(h.shaderCompileErrors_).to.eql(null);
});
it('can find the uniform location', function() {
expect(h.getUniformLocation('u_test')).to.not.eql(null);
});
@@ -162,9 +164,7 @@ describe('ol.webgl.WebGLHelper', function() {
beforeEach(function() {
h = new WebGLHelper();
const vertexShader = new WebGLVertex(INVALID_VERTEX_SHADER);
const fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
p = h.getProgram(fragmentShader, vertexShader);
p = h.getProgram(FRAGMENT_SHADER, INVALID_VERTEX_SHADER);
h.useProgram(p);
});
@@ -172,12 +172,12 @@ describe('ol.webgl.WebGLHelper', function() {
expect(h.currentProgram_).to.eql(p);
});
it('cannot find the uniform location', function() {
expect(h.getUniformLocation('u_test')).to.eql(null);
it('has shader compilation errors', function() {
expect(h.shaderCompileErrors_).to.not.eql(null);
});
it('cannot find the attribute location', function() {
expect(h.getAttributeLocation('a_test')).to.eql(-1);
it('cannot find the uniform location', function() {
expect(h.getUniformLocation('u_test')).to.eql(null);
});
});