WebGL / Introduced the WebGLRenderTarget class
This utility class simplifies rendering to a texture & reading the results of the render. It also allows clearing its content before a new render.
This commit is contained in:
124
src/ol/webgl/RenderTarget.js
Normal file
124
src/ol/webgl/RenderTarget.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* A wrapper class to simplify rendering to a texture instead of the final canvas
|
||||||
|
* @module ol/webgl/RenderTarget
|
||||||
|
*/
|
||||||
|
import {equals} from '../array.js';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @classdesc
|
||||||
|
* This class is a wrapper around the association of both a `WebGLTexture` and a `WebGLFramebuffer` instances,
|
||||||
|
* simplifying initialization and binding for rendering.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
class WebGLRenderTarget {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("./Helper.js").default} helper WebGL helper; mandatory.
|
||||||
|
* @param {Array<number>} [opt_size] Expected size of the render target texture; note: this can be changed later on.
|
||||||
|
*/
|
||||||
|
constructor(helper, opt_size) {
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {import("./Helper.js").default}
|
||||||
|
*/
|
||||||
|
this.helper_ = helper;
|
||||||
|
const gl = helper.getGL();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {WebGLTexture}
|
||||||
|
*/
|
||||||
|
this.texture_ = gl.createTexture();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {WebGLFramebuffer}
|
||||||
|
*/
|
||||||
|
this.framebuffer_ = gl.createFramebuffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<number>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.size_ = opt_size || [1, 1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Uint8Array}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.data_ = new Uint8Array(1);
|
||||||
|
|
||||||
|
this.updateSize_();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the size of the render target texture. Note: will do nothing if the size
|
||||||
|
* is already the same.
|
||||||
|
* @param {Array<number>} size Expected size of the render target texture
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
setSize(size) {
|
||||||
|
if (equals(size, this.size_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.size_[0] = size[0];
|
||||||
|
this.size_[1] = size[1];
|
||||||
|
this.updateSize_();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of the render target texture
|
||||||
|
* @return {Array<number>} Size of the render target texture
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
getSize() {
|
||||||
|
return this.size_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the content of the render target texture as raw data (series of r,g,b,a values)
|
||||||
|
* @return {Uint8Array} Integer array of color values
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
read() {
|
||||||
|
const size = this.size_;
|
||||||
|
const gl = this.helper_.getGL();
|
||||||
|
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer_);
|
||||||
|
gl.readPixels(0, 0, size[0], size[1], gl.RGBA, gl.UNSIGNED_BYTE, this.data_);
|
||||||
|
return this.data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {WebGLTexture} Texture to render to
|
||||||
|
*/
|
||||||
|
getTexture() {
|
||||||
|
return this.texture_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {WebGLFramebuffer} Frame buffer of the render target
|
||||||
|
*/
|
||||||
|
getFramebuffer() {
|
||||||
|
return this.framebuffer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
updateSize_() {
|
||||||
|
const size = this.size_;
|
||||||
|
const gl = this.helper_.getGL();
|
||||||
|
|
||||||
|
this.texture_ = this.helper_.createTexture(size, null, this.texture_);
|
||||||
|
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer_);
|
||||||
|
gl.viewport(0, 0, size[0], size[1]);
|
||||||
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture_, 0);
|
||||||
|
|
||||||
|
this.data_ = new Uint8Array(size[0] * size[1] * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebGLRenderTarget;
|
||||||
80
test/spec/ol/webgl/rendertarget.test.js
Normal file
80
test/spec/ol/webgl/rendertarget.test.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import WebGLRenderTarget from '../../../../src/ol/webgl/RenderTarget.js';
|
||||||
|
import WebGLHelper from '../../../../src/ol/webgl/Helper.js';
|
||||||
|
|
||||||
|
|
||||||
|
describe('ol.webgl.RenderTarget', function() {
|
||||||
|
let helper, testImage_4x4;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
helper = new WebGLHelper();
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
testImage_4x4 = canvas.getContext('2d').createImageData(4, 4);
|
||||||
|
for (let i = 0; i < testImage_4x4.data.length; i += 4) {
|
||||||
|
testImage_4x4.data[i] = 100;
|
||||||
|
testImage_4x4.data[i + 1] = 150;
|
||||||
|
testImage_4x4.data[i + 2] = 200;
|
||||||
|
testImage_4x4.data[i + 3] = 250;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', function() {
|
||||||
|
|
||||||
|
it('creates a target of size 1x1', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper);
|
||||||
|
expect(rt.getSize()).to.eql([1, 1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a target of specified size', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper, [12, 34]);
|
||||||
|
expect(rt.getSize()).to.eql([12, 34]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#setSize', function() {
|
||||||
|
|
||||||
|
it('updates the target size', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper, [12, 34]);
|
||||||
|
expect(rt.getSize()).to.eql([12, 34]);
|
||||||
|
rt.setSize([45, 67]);
|
||||||
|
expect(rt.getSize()).to.eql([45, 67]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does nothing if the size has not changed', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper, [12, 34]);
|
||||||
|
const spy = sinon.spy(rt, 'updateSize_');
|
||||||
|
rt.setSize([12, 34]);
|
||||||
|
expect(spy.called).to.be(false);
|
||||||
|
rt.setSize([12, 345]);
|
||||||
|
expect(spy.called).to.be(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#readData', function() {
|
||||||
|
|
||||||
|
it('returns 1-pixel data with the default options', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper);
|
||||||
|
expect(rt.read().length).to.eql(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the content of the texture', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper, [4, 4]);
|
||||||
|
helper.createTexture([4, 4], testImage_4x4, rt.getTexture());
|
||||||
|
const data = rt.read();
|
||||||
|
|
||||||
|
expect(data[0]).to.eql(100);
|
||||||
|
expect(data[1]).to.eql(150);
|
||||||
|
expect(data[2]).to.eql(200);
|
||||||
|
expect(data[3]).to.eql(250);
|
||||||
|
expect(data[4]).to.eql(100);
|
||||||
|
expect(data[5]).to.eql(150);
|
||||||
|
expect(data[6]).to.eql(200);
|
||||||
|
expect(data[7]).to.eql(250);
|
||||||
|
expect(data.length).to.eql(4 * 4 * 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user