Skip to content

Commit

Permalink
Copy canvas data into intermediary ImageData buffer
Browse files Browse the repository at this point in the history
Certain GPUs don't render HTMLCanvasElements with TexImage2D/TexSubImage2D correctly. This mitigates that by copying canvas data into an intermediary ImageData buffer.
  • Loading branch information
Lauren Budorick authored Aug 29, 2017
1 parent 41c6ce1 commit 9b7e2e3
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 6 deletions.
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'
* });
*
* // 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

0 comments on commit 9b7e2e3

Please sign in to comment.