Webgl / improve reading of render targets data
Now two methods are available: `readAll` and `readPixel`, and the data from the render target is not re-read every time unless `clearCachedData` is called.
This commit is contained in:
@@ -4,6 +4,8 @@
|
|||||||
*/
|
*/
|
||||||
import {equals} from '../array.js';
|
import {equals} from '../array.js';
|
||||||
|
|
||||||
|
// for pixel color reading
|
||||||
|
const tmpArray4 = new Uint8Array(4);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @classdesc
|
* @classdesc
|
||||||
@@ -47,7 +49,13 @@ class WebGLRenderTarget {
|
|||||||
* @type {Uint8Array}
|
* @type {Uint8Array}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.data_ = new Uint8Array(1);
|
this.data_ = new Uint8Array(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.dataCacheDirty_ = true;
|
||||||
|
|
||||||
this.updateSize_();
|
this.updateSize_();
|
||||||
}
|
}
|
||||||
@@ -77,19 +85,50 @@ class WebGLRenderTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the content of the render target texture as raw data (series of r,g,b,a values)
|
* This will cause following calls to `#readAll` or `#readPixel` to download the content of the
|
||||||
|
* render target into memory, which is an expensive operation.
|
||||||
|
* This content will be kept in cache but should be cleared after each new render.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
clearCachedData() {
|
||||||
|
this.dataCacheDirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the full content of the frame buffer as a series of r, g, b, a components
|
||||||
|
* in the 0-255 range (unsigned byte).
|
||||||
* @return {Uint8Array} Integer array of color values
|
* @return {Uint8Array} Integer array of color values
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
read() {
|
readAll() {
|
||||||
const size = this.size_;
|
if (this.dataCacheDirty_) {
|
||||||
const gl = this.helper_.getGL();
|
const size = this.size_;
|
||||||
|
const gl = this.helper_.getGL();
|
||||||
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer_);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer_);
|
||||||
gl.readPixels(0, 0, size[0], size[1], gl.RGBA, gl.UNSIGNED_BYTE, this.data_);
|
gl.readPixels(0, 0, size[0], size[1], gl.RGBA, gl.UNSIGNED_BYTE, this.data_);
|
||||||
|
this.dataCacheDirty_ = false;
|
||||||
|
}
|
||||||
return this.data_;
|
return this.data_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads one pixel of the frame buffer as an array of r, g, b, a components
|
||||||
|
* in the 0-255 range (unsigned byte).
|
||||||
|
* @param {number} x Pixel coordinate
|
||||||
|
* @param {number} y Pixel coordinate
|
||||||
|
* @returns {Uint8Array} Integer array with one color value (4 components)
|
||||||
|
*/
|
||||||
|
readPixel(x, y) {
|
||||||
|
this.readAll();
|
||||||
|
const index = Math.floor(x) + (this.size_[1] - Math.floor(y) - 1) * this.size_[0];
|
||||||
|
tmpArray4[0] = this.data_[index * 4];
|
||||||
|
tmpArray4[1] = this.data_[index * 4 + 1];
|
||||||
|
tmpArray4[2] = this.data_[index * 4 + 2];
|
||||||
|
tmpArray4[3] = this.data_[index * 4 + 3];
|
||||||
|
return tmpArray4;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {WebGLTexture} Texture to render to
|
* @return {WebGLTexture} Texture to render to
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ describe('ol.webgl.RenderTarget', function() {
|
|||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
testImage_4x4 = canvas.getContext('2d').createImageData(4, 4);
|
testImage_4x4 = canvas.getContext('2d').createImageData(4, 4);
|
||||||
for (let i = 0; i < testImage_4x4.data.length; i += 4) {
|
for (let i = 0; i < testImage_4x4.data.length; i += 4) {
|
||||||
testImage_4x4.data[i] = 100;
|
testImage_4x4.data[i] = 100 + i / 4;
|
||||||
testImage_4x4.data[i + 1] = 150;
|
testImage_4x4.data[i + 1] = 100 + i / 4;
|
||||||
testImage_4x4.data[i + 2] = 200;
|
testImage_4x4.data[i + 2] = 200 + i / 4;
|
||||||
testImage_4x4.data[i + 3] = 250;
|
testImage_4x4.data[i + 3] = 200 + i / 4;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,29 +52,77 @@ describe('ol.webgl.RenderTarget', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#readData', function() {
|
describe('#readAll', function() {
|
||||||
|
|
||||||
it('returns 1-pixel data with the default options', function() {
|
it('returns 1-pixel data with the default options', function() {
|
||||||
const rt = new WebGLRenderTarget(helper);
|
const rt = new WebGLRenderTarget(helper);
|
||||||
expect(rt.read().length).to.eql(4);
|
expect(rt.readAll().length).to.eql(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the content of the texture', function() {
|
it('returns the content of the texture', function() {
|
||||||
const rt = new WebGLRenderTarget(helper, [4, 4]);
|
const rt = new WebGLRenderTarget(helper, [4, 4]);
|
||||||
helper.createTexture([4, 4], testImage_4x4, rt.getTexture());
|
helper.createTexture([4, 4], testImage_4x4, rt.getTexture());
|
||||||
const data = rt.read();
|
const data = rt.readAll();
|
||||||
|
|
||||||
expect(data[0]).to.eql(100);
|
expect(data[0]).to.eql(100);
|
||||||
expect(data[1]).to.eql(150);
|
expect(data[1]).to.eql(100);
|
||||||
expect(data[2]).to.eql(200);
|
expect(data[2]).to.eql(200);
|
||||||
expect(data[3]).to.eql(250);
|
expect(data[3]).to.eql(200);
|
||||||
expect(data[4]).to.eql(100);
|
expect(data[4]).to.eql(101);
|
||||||
expect(data[5]).to.eql(150);
|
expect(data[5]).to.eql(101);
|
||||||
expect(data[6]).to.eql(200);
|
expect(data[6]).to.eql(201);
|
||||||
expect(data[7]).to.eql(250);
|
expect(data[7]).to.eql(201);
|
||||||
expect(data.length).to.eql(4 * 4 * 4);
|
expect(data.length).to.eql(4 * 4 * 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not call gl.readPixels again when #clearCachedData is not called', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper, [4, 4]);
|
||||||
|
helper.createTexture([4, 4], testImage_4x4, rt.getTexture());
|
||||||
|
const spy = sinon.spy(rt.helper_.getGL(), 'readPixels');
|
||||||
|
rt.readAll();
|
||||||
|
expect(spy.callCount).to.eql(1);
|
||||||
|
rt.readAll();
|
||||||
|
expect(spy.callCount).to.eql(1);
|
||||||
|
rt.clearCachedData();
|
||||||
|
rt.readAll();
|
||||||
|
expect(spy.callCount).to.eql(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#readPixel', function() {
|
||||||
|
|
||||||
|
it('returns the content of one pixel', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper, [4, 4]);
|
||||||
|
helper.createTexture([4, 4], testImage_4x4, rt.getTexture());
|
||||||
|
|
||||||
|
let data = rt.readPixel(0, 0);
|
||||||
|
expect(data[0]).to.eql(112);
|
||||||
|
expect(data[1]).to.eql(112);
|
||||||
|
expect(data[2]).to.eql(212);
|
||||||
|
expect(data[3]).to.eql(212);
|
||||||
|
|
||||||
|
data = rt.readPixel(3, 3);
|
||||||
|
expect(data[0]).to.eql(103);
|
||||||
|
expect(data[1]).to.eql(103);
|
||||||
|
expect(data[2]).to.eql(203);
|
||||||
|
expect(data[3]).to.eql(203);
|
||||||
|
expect(data.length).to.eql(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not call gl.readPixels again when #clearCachedData is not called', function() {
|
||||||
|
const rt = new WebGLRenderTarget(helper, [4, 4]);
|
||||||
|
helper.createTexture([4, 4], testImage_4x4, rt.getTexture());
|
||||||
|
const spy = sinon.spy(rt.helper_.getGL(), 'readPixels');
|
||||||
|
rt.readPixel(0, 0);
|
||||||
|
expect(spy.callCount).to.eql(1);
|
||||||
|
rt.readPixel(1, 1);
|
||||||
|
expect(spy.callCount).to.eql(1);
|
||||||
|
rt.clearCachedData();
|
||||||
|
rt.readPixel(2, 2);
|
||||||
|
expect(spy.callCount).to.eql(2);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user