Merge pull request #8977 from jahow/add-webgl-rendering-tests
Add WebGL rendering tests & improve the WebGLHelper API
This commit is contained in:
BIN
rendering/cases/heatmap-layer/expected.png
Normal file
BIN
rendering/cases/heatmap-layer/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
44
rendering/cases/heatmap-layer/main.js
Normal file
44
rendering/cases/heatmap-layer/main.js
Normal 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'
|
||||
});
|
||||
BIN
rendering/cases/webgl-points/expected.png
Normal file
BIN
rendering/cases/webgl-points/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
47
rendering/cases/webgl-points/main.js
Normal file
47
rendering/cases/webgl-points/main.js
Normal 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'
|
||||
});
|
||||
1
rendering/data/2012_Earthquakes_Mag5.kml
Symbolic link
1
rendering/data/2012_Earthquakes_Mag5.kml
Symbolic link
@@ -0,0 +1 @@
|
||||
../../examples/data/kml/2012_Earthquakes_Mag5.kml
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user