Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy canvas data into intermediary ImageData buffer #5155

Merged
merged 4 commits into from
Aug 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion flow-typed/style-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ declare type CanvasSourceSpecification = {|
"type": "canvas",
"coordinates": [[number, number], [number, number], [number, number], [number, number]],
"animate"?: boolean,
"canvas": string
"canvas": string,
"contextType": "2d" | "webgl" | "experimental-webgl" | "webgl2"
|}

declare type SourceSpecification =
Expand Down
33 changes: 31 additions & 2 deletions src/source/canvas_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import type Evented from '../util/evented';
* [-76.52, 39.18],
* [-76.52, 39.17],
* [-76.54, 39.17]
* ]
* ],
* contextType: '2d'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have to make a breaking change here, maybe it's better to replace the canvas property with a context property of type CanvasRenderingContext2D | WebGLRenderingContext. On the other hand, that would be a property that's not JSON-serializable...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intuitively I think I prefer the JSON-serializable string, and feel okay with the string enum options since it must match the string that was used to access the drawing context in the first place 🤔 though I'm happy to defer to stronger opinions…

* });
*
* // update
Expand All @@ -40,6 +41,8 @@ class CanvasSource extends ImageSource {
options: CanvasSourceSpecification;
animate: boolean;
canvas: HTMLCanvasElement;
context: (CanvasRenderingContext2D | WebGLRenderingContext);
secondaryContext: ?CanvasRenderingContext2D;
width: number;
height: number;
play: () => void;
Expand All @@ -53,6 +56,9 @@ class CanvasSource extends ImageSource {

load() {
this.canvas = this.canvas || window.document.getElementById(this.options.canvas);
const context = this.canvas.getContext(this.options.contextType);
if (!context) return this.fire('error', new Error('Canvas context not found.'));
this.context = context;
this.width = this.canvas.width;
this.height = this.canvas.height;
if (this._hasInvalidDimensions()) return this.fire('error', new Error('Canvas dimensions cannot be less than or equal to zero.'));
Expand Down Expand Up @@ -101,6 +107,24 @@ class CanvasSource extends ImageSource {
*/
// setCoordinates inherited from ImageSource

readCanvas() {
// We *should* be able to use a pure HTMLCanvasElement in
// texImage2D/texSubImage2D (in ImageSource#_prepareImage), but for
// some reason this breaks the map on certain GPUs (see #4262).

if (this.context instanceof CanvasRenderingContext2D) {
return this.context.getImageData(0, 0, this.width, this.height);
} else if (this.context instanceof WebGLRenderingContext) {
const gl = this.context;
const data = new Uint8Array(this.width * this.height * 4);
gl.readPixels(0, 0, this.width, this.height, gl.RGBA, gl.UNSIGNED_BYTE, data);
if (!this.secondaryContext) this.secondaryContext = window.document.createElement('canvas').getContext('2d');
const imageData = this.secondaryContext.createImageData(this.width, this.height);
imageData.data.set(data);
return imageData;
}
}

prepare() {
let resize = false;
if (this.canvas.width !== this.width) {
Expand All @@ -114,7 +138,12 @@ class CanvasSource extends ImageSource {
if (this._hasInvalidDimensions()) return;

if (Object.keys(this.tiles).length === 0) return; // not enough data for current position
this._prepareImage(this.map.painter.gl, this.canvas, resize);
const canvasData = this.readCanvas();
if (!canvasData) {
this.fire('error', new Error('Could not read canvas data.'));
return;
}
this._prepareImage(this.map.painter.gl, canvasData, resize);
}

serialize(): Object {
Expand Down
4 changes: 2 additions & 2 deletions src/source/image_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class ImageSource extends Evented implements Source {
this._prepareImage(this.map.painter.gl, this.image);
}

_prepareImage(gl: WebGLRenderingContext, image: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, resize?: boolean) {
_prepareImage(gl: WebGLRenderingContext, image: HTMLImageElement | HTMLVideoElement | ImageData, resize?: boolean) {
if (!this.boundsBuffer) {
this.boundsBuffer = new VertexBuffer(gl, this._boundsArray);
}
Expand All @@ -201,7 +201,7 @@ class ImageSource extends Evented implements Source {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
} else if (resize) {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
} else if (image instanceof window.HTMLVideoElement || image instanceof window.ImageData || image instanceof window.HTMLCanvasElement) {
} else if (image instanceof window.HTMLVideoElement || image instanceof window.ImageData) {
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, image);
}
Expand Down
19 changes: 19 additions & 0 deletions src/style-spec/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,25 @@
"type": "string",
"required": true,
"doc": "HTML ID of the canvas from which to read pixels."
},
"contextType": {
"required": true,
"type": "enum",
"values": {
"2d": {
"doc" : "Source canvas is associated with a `2d` drawing context."
},
"webgl": {
"doc": "Source canvas is associated with a `webgl` drawing context."
},
"experimental-webgl": {
"doc": "Source canvas is associated with an `experimental-webgl` drawing context."
},
"webgl2": {
"doc": "Source canvas is associated with a `webgl2` drawing context."
}
},
"doc": "The context identifier defining the drawing context associated to the source canvas (see HTMLCanvasElement.getContext() documentation)."
}
},
"layer": {
Expand Down
3 changes: 2 additions & 1 deletion test/unit/source/canvas_source.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ function createSource(options) {

options = util.extend({
canvas: 'id',
coordinates: [[0, 0], [1, 0], [1, 1], [0, 1]]
coordinates: [[0, 0], [1, 0], [1, 1], [0, 1]],
contextType: '2d'
}, options);

const source = new CanvasSource('id', options, { send: function() {} }, options.eventedParent);
Expand Down