Is there any maps per page limitation in mapbox-gl? - javascript

I'm trying to have 17 small maps on the same page using mapbox-gl and facing:
WARNING: Too many active WebGL contexts. Oldest context will be lost.
Uncaught TypeError: Failed to execute 'shaderSource' on 'WebGLRenderingContext': parameter 1 is not of type 'WebGLShader'.
at new Program (mapbox-gl.js:182)
at Painter._createProgramCached (mapbox-gl.js:178)
at Painter.useProgram (mapbox-gl.js:178)
at setFillProgram (mapbox-gl.js:154)
at drawFillTile (mapbox-gl.js:154)
at drawFillTiles (mapbox-gl.js:154)
at Object.drawFill [as fill] (mapbox-gl.js:154)
at Painter.renderLayer (mapbox-gl.js:178)
at Painter.render (mapbox-gl.js:178)
at e._render (mapbox-gl.js:497)
I had the same issue when i tried to have many google streetview galleries on the same page, but as my streetview shouldn't be visible at the same moment i ended using the same streetview changing address dynamically.
But for maps list requirement is to show that many maps to user. Can't show them one by one. Not sure how i could work out that issue.
i'm using mapbox-gl#0.45.0, and testing it in chrome Version 66.0.3359.181 (Official Build) (64-bit) on Mac OS Sierra 10.12.6 (16G1036)

I'm going to guess you are out of luck. Browsers limit the number of WebGL instances. There are workarounds but to use them would probably require changes to the way mapbox-gl is implemented. I suggest you ask them if they'd consider implementing one of the workarounds assuming they haven't already.
There is one other possibility that comes to mind and that would be to do your own virtualization of WebGL in JavaScript. That's probably not a good solution though because it wouldn't share resources across maps and it might be too heavy.
Off the top of my head you'd have to create an offscreen canvas and override HTMLCanvasElement.prototype.getContext so that when someone makes a webgl context you return a virtual context. You'd wrap every function and if that virtual context doesn't match the last used virtual context you'd save all the webgl state and restore the state for the new context. You'd also have to keep framebuffers to match the drawingbuffer for each canvas, bind them when the current framebuffer binding is null and resize them if the canvas sized changed, and then render to the offscreen canvas and then canvas2d.drawImage to their respective canvases anytime the current event exits. It's that last part that would be heaviest.
In semi-pseudo code
// This is just off the top of my head and is just pseudo code
// but hopefully gives an idea of how to virtualize WebGL.
const canvasToVirtualContextMap = new Map();
let currentVirtualContext = null;
let sharedWebGLContext;
const baseState = makeDefaultState();
HTMLCanvasElement.prototype.getContext = (function(origFn) {
return function(type, contextAttributes) {
if (type === 'webgl') {
return createOrGetVirtualWebGLContext(this, type, contextAttributes);
}
return origFn.call(this, contextAttributes);
};
}(HTMLCanvasElement.prototype.getContext));
class VirutalWebGLContext {
constructor(cavnas, contextAttributes) {
this.canvas = canvas;
// based on context attributes and canvas.width, canvas.height
// create a texture and framebuffer
this._drawingbufferTexture = ...;
this._drawingbufferFramebuffer = ...;
// remember all WebGL state (default bindings, default texture units,
// default attributes and/or vertex shade object, default program,
// default blend, stencil, zbuffer, culling, viewport etc... state
this._state = makeDefaultState();
}
}
function makeDefaultState() {
const state ={};
state[WebGLRenderingContext.ARRAY_BUFFER] = null;
... tons more ...
}
// copy all WebGL constants and functions to the prototype of
// VirtualWebGLContext
for (let key in WebGLRenderingContext.protoype) {
const value = WebGLRenderingContext.prototype[key];
let newValue = value;
switch (key) {
case 'bindFramebuffer':
newValue = virutalBindFramebuffer;
break;
case 'clear':
case 'drawArrays':
case 'drawElements':
newValue = createDrawWrapper(value);
break;
default:
if (typeof value === 'function') {
newValue = createWrapper(value);
}
break;
}
VirtualWebGLContext.prototype[key] = newValue;
}
function virutalBindFramebuffer(bindpoint, framebuffer) {
if (bindpoint === WebGLRenderingContext.FRAMEBUFFER) {
if (target === null) {
// bind our drawingBuffer
sharedWebGLContext.bindFramebuffer(bindpoint, this._drawingbufferFramebuffer);
}
}
sharedWebGLContext.bindFramebuffer(bindpoint, framebuffer);
}
function createWrapper(origFn) {
// lots of optimization could happen here depending on specific functions
return function(...args) {
makeCurrentContext(this);
resizeCanvasIfChanged(this);
return origFn.call(sharedWebGLContext, ...args);
};
}
function createDrawWrapper(origFn) {
const newFn = createWrapper(origFn);
return function(...args) {
// a rendering function was called so we need to copy are drawingBuffer
// to the canvas for this context after the current event.
this._needComposite = true;
return newFn.call(this, ...args);
};
}
function makeCurrentContext(vctx) {
if (currentVirtualContext === vctx) {
return;
}
// save all current WebGL state on the previous current virtual context
saveAllState(currentVirutalContext._state);
// restore all state for the
restoreAllState(vctx._state);
// check if the current state is supposed to be rendering to the canvas.
// if so bind vctx._drawingbuffer
currentVirtualContext = vctx;
}
function resizeCanvasIfChanged(vctx) {
if (canvas.width !== vtx._width || canvas.height !== vctx._height) {
// resize this._drawingBuffer to match the new canvas size
}
}
function createOrGetVirtualWebGLContext(canvas, type, contextAttributes) {
// check if this canvas already has a context
const existingVirtualCtx = canvasToVirtualContextMap.get(canvas);
if (existingVirtualCtx) {
return existingVirtualCtx;
}
if (!sharedWebGLContext) {
sharedWebGLContext = document.createElement("canvas").getContext("webgl");
}
const newVirtualCtx = new VirtualWebGLContext(canvas, contextAttributes);
canvasToVirtualContextMap.set(canvas, newVirtualCtx);
return newVirtualCtx;
}
function saveAllState(state) {
// save all WebGL state (current bindings, current texture units,
// current attributes and/or vertex shade object, current program,
// current blend, stencil, zbuffer, culling, viewport etc... state
state[WebGLRenderingContext.ARRAY_BUFFER] = sharedGLState.getParameter(gl.ARRAY_BUFFER_BINDING);
state[WebGLRenderingContext.TEXTURE_2D] = sharedGLState.getParameter(gl.TEXTURE_BINDING_2D);
... tons more ...
}
function restoreAllState(state) {
// resture all WebGL state (current bindings, current texture units,
// current attributes and/or vertex shade object, current program,
// current blend, stencil, zbuffer, culling, viewport etc... state
gl.bindArray(gl.ARRAY_BUFFER, state[WebGLRenderingContext.ARRAY_BUFFER]);
gl.bindTexture(gl.TEXTURE_2D, state[WebGLRenderingContext.TEXTURE_2D]);
... tons more ...
}
function renderAllDirtyVirtualCanvas() {
let setup = false;
for (const vctx of canvasToVirtualContextMap.values()) {
if (!vctx._needComposite) {
continue;
}
vctx._needComposite = false;
if (!setup) {
setup = true;
// save all current WebGL state on the previous current virtual context
saveAllState(currentVirutalContext._state);
currentVirutalContext = null;
// set the state back to the default
restoreAllState(sharedGlContext, baseState);
// setup whatever state we need to render vctx._drawinbufferTexture
// to the canvas.
sharedWebGLContext.useProgram(programToRenderCanvas);
...
}
// draw the drawingbuffer's texture to the canvas
sharedWebGLContext.bindTexture(gl.TEXTURE_2D, vctx._drawingbufferTexture);
sharedWebGLContext.drawArrays(gl.TRIANGLES, 0, 6);
}
}
you'd also need to trap events that cause rendering which would be unique to each app. If the app uses requetsAnimationFrame to render then maybe something like
window.requestAnimationFrame = (function(origFn) {
return function(callback) {
return origFn.call(window, (time) {
const result = callback(time);
renderAllDirtyVirtualCanvases();
return result;
};
};
}(window.requestAnimationFrame));
If the app renders on other events, like say mousemove then maybe
something like this
let someContextNeedsRendering;
function createDrawWrapper(origFn) {
const newFn = createWrapper(origFn);
return function(...args) {
// a rendering function was called so we need to copy are drawingBuffer
// to the canvas for this context after the current event.
this._needComposite = true;
if (!someContextsNeedRendering) {
someContextsNeedRendering = true;
setTimeout(dealWithDirtyContexts, 0);
}
return newFn.call(this, ...args);
};
}
function dealWithDirtyContexts() {
someContextsNeedRendering = false;
renderAllDirtyVirtualCanvas();
});
Makes me wonder if someone else has already done this.

Related

Overriding CanvasRenderingContext2D.getImageData()

I am trying to override the built in method CanvasRenderingContext2D.getImageData(). I would like to override the implementation so that the modified function uses the canvas context to modify the canvas and then calls the original function which should return different data that if the function was not overridden. The reason I am doing this is to prevent browser fingerprinting.
canvas.js
(function(){
'use strict';
var originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
// This function just adds 1 to each RGBA component in the array for testing.
// Will add random values for the real thing.
function randomiseImageData(image) {
var imageData = image.data;
var imageLength = imageData.length;
for (var i = 0; i < imageLength; i++) {
imageData[i] += 1;
}
var modifiedImage = new ImageData(image.width, image.height);
return modifiedImage;
}
CanvasRenderingContext2D.prototype.getImageData = function(sx, sy, sw, sh) {
console.log("[ALERT] " + window.location.hostname + " called CanvasRenderingContext2D.getImageData()");
const origin = window.location.hostname;
Math.seedrandom(origin);
var image = originalGetImageData.call(this, sx, sy, sw, sh);
return randomiseImageData(image);
};
})();
You are returning a new empty ImageData object.
I guess what you want is to return the filled one.
Since you already modified the data array, you can simply return the original ImageData, your modifications will have been made.
// shortened version
(function(){
const ori = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function(){
let imageData = ori.apply(this, arguments);
// modify the Uint8Array
imageData.data.forEach((v, i, a) => a[i]+=1);
// return the now modified ImageData
return imageData;
};
})()
var ctx = document.createElement('canvas').getContext('2d');
console.log(ctx.getImageData(0,0,1,1));
If you really want to create a new ImageData, then it's
new ImageData(imageData, image.width, image.height);
// ^^
// pass the data to fill the new ImageData object
But note that browser support is not great, and that you won't win anything by doing so.
You can not remove the fingerprint.
For more The Web never forgets
You can not circumvent fingerprinting. The best you can do is return the most common fingerprint (which is not easily ascertained) increasing the set of devices you may belong to.
Returning a random set of pixels (or incrementing each pixel channel by one) is about the worst you can do if you are the only one doing it. It would absolutly mark your browser as unique and would let traking software know that the browser that returns changed data is just one, or one of a very small set.
The best way to stop fingerprinting is via a common and widely adopted data return strategy. If every browser returned all zero (transparent black) then there would be no uniqueness and thus no way to track the device based on the canvas.
Canvas fingerprinting is only part of a fingerprint, there are many more sources of data the help identify a device. The browser, browser version, OS, OS version, screen resolution, and a long list of others. Even if you eliminate the canvas as a source of uniqueness it is pointless unless you do the same with the rest of the information.
Mitigation
So with that said the code to return zeroed data is as follows.
(function () {
if (window.CanvasRenderingContext2D) {
const gid = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function (x, y, w, h) {
var data = gid.bind(this)(x, y, w, h);
data.data.fill(0); // fill with zero
return data;
}
// Token way to avoid JS from finding out that you have overwritten the prototype overwrite
// the toString method as well (note ctx.getImageData.toString.toString() will
// still show you have changed the prototype but over writing Object.toSting is not worth the problems)
CanvasRenderingContext2D.prototype.getImageData.toString = function () {
return "function getImageData() { [native code] }";
}
}
}());

bacon.js: Strange behaviour with holdWhen and onValue

Take a look at the working CodePen here: http://codepen.io/djskinner/pen/JdpwyY
// Animation start events push here
var startBus = new Bacon.Bus();
// Animation end events push here
var endBus = new Bacon.Bus();
// Balance updates push here
var balanceBus = new Bacon.Bus();
// A Property that determines if animating or not
var isAnimating = Bacon.update(false,
[startBus], function() { return true; },
[endBus], function() { return false; }
);
// Only update the displayBalance when not animating
var displayBalance = Bacon.update(0,
[balanceBus.holdWhen(isAnimating)], function(previous, x) {
return x;
}
);
setTimeout(function() {
var streamTemplate = Bacon.combineTemplate({
balance: displayBalance
});
// Uncommenting this block changes the way the system behaves
// streamTemplate.onValue(function(initialState) {
// console.log(initialState);
//})();
// Print the displayBalance
streamTemplate.onValue(function(v) {
console.log(v.balance);
});
});
Pressing the balance button generates a new random number. A Property is created that uses holdWhen to restrict balance updates coming through until the isAnimating Property becomes false.
If I was interested in getting the initial state of streamTemplate, I might get the value and immediately unsubscribe:
streamTemplate.onValue(function(initialState) {
console.log(initialState);
})();
However, once I do this the displayBalance Property behaves differently and I no longer receive updates.
Why would this seemingly inert change make such a drastic different to the system? Surely the behaviour of the system shouldn't be dependent on whether someone has subscribe and unsubscribed to the streamTemplate at some point in the past?
This behaviour has been confirmed as a bug that has been fixed in 0.7.67.
See here for details.

Handling Complex Dependencies Between Object Properties (Auto Update Dependent Properties)

I have a tree structure of objects, and their properties have very complicated dependencies on surrounding objects determined by where they are in the tree. I have hard coded a lot of these dependencies, and tried to create some sort of update loop (where if a property gets updated, based on the design, all of the properties that depend on it get updated, and in the correct order), but I want to handle it in a more generic/abstract way, instead of hard coding a bunch of update calls to different objects.
Let's say, for example, I have 1 superclass, and 3 subclasses, and then a separate container object.
Shape
properties: parentContainer, index, left, top, width, height
methods: updateLeft(), updateTop(), updateWidth(), updateHeight()
Square inherits from Shape
Triangle inherits from Shape
Circle inherits from Shape
ShapeContainer
properties: shapes
methods: addShape(shape, index), removeShape(index)
I'll give a pseudocode example update method to illustrate how these dependencies crop up:
Square.updateTop() {
var prevShape = null;
if (this.index != 0) {
prevShape = this.parentContainer.shapes[this.index - 1];
}
var nextSquareInContainer = null;
for (var i = this.index; i < this.parentContainer.shapes.length; i++) {
var shape = this.parentContainer.shapes[i];
if(shape instanceof Square) {
nextSquareInContainer = shape;
break;
}
}
var top = 0;
if (prevShape != null && nextSquareInContainer != null) {
top = prevShape.top + nextSquareInContainer.width;
} else {
top = 22;
}
this.top = top;
}
So, any square objects added to the shapeConatiner will depend on the previous shape's top value and the next square found in the container's width value for its top value.
Here is some code to set up an example shape container:
var shapeContainer = new ShapeContainer();
var triangle = new Triangle();
var circle = new Circle();
var square1 = new Square();
var square2 = new Square();
shapeContainer.addShape(triangle, 0);
shapeContainer.addShape(circle, 1);
shapeContainer.addShape(square1, 2);
shapeContainer.addShape(square2, 3);
So, I guess the crux of the issue is, if I update the above circle's top value, I want the top value of square1 to be automatically updated (because there is a one way dependency between square1's top value, and circle's top value). So one way I can do this (the way I've been doing it, in combination with some other specific knowledge of my problem domain to simplify the calls), is to add the code similar to the following to Circle's updateTop method (really it would have to be added to each shape's updateTop method):
Circle.updateTop() {
// Code to actually calculate and update Circle's top value, note this
// may depend on its own set of dependencies
var nextShape = this.parentContainer.shapes[this.index + 1];
if (nextShape instanceof Square) {
nextShape.updateTop();
}
}
This type of design is fine for a few simple dependencies between objects, but my project has dozens of types of objects with probably hundreds of dependencies between their properties. I've coded it this way, but it is very difficult to reason about when trying to add new features, or troubleshoot a bug.
Is there some sort of design pattern out there to set up dependencies between object properties, and then when one property is updated, it updates all of the properties on other objects that depend on it (which may then trigger further updating of properties that depend on the now newly updated properties)? Some sort of declarative syntax for specifying these dependencies would probably be best for readability/maintainability.
Another issue is, a property may have several dependencies, that ALL must be updated before I want that property to update itself.
I've been looking into a pub/sub type of solution, but I thought this was a complicated enough problem to reach out for help. As a side note, I'm working in javascript.
Here is the hackish solution I came up with. I create a wrapper class, that you pass in anonymous functions for getter/setter/updaters. Then you make a call of prop1.dependsOn(prop2) to declaratively set up dependencies. It involves setting up a directed acyclic graph of the dependencies between object properties, and then when a property value is updated, explicitly making a call to resolve the related dependencies using a topological sort. I didn't put much thought into efficiency, and I bet somebody could come up with a much more robust/performant solution, but I think this will do for now. Sorry for the code dump, but I thought it could be of some help to somebody trying to solve a similar problem down the road. If somebody wants to make this syntactically cleaner, be my guest.
// This is a class that will act as a wrapper for all properties
// that we want to tie to our dependency graph.
function Property(initialValue, ctx) {
// Each property will get a unique id.
this.id = (++Property.id).toString();
this.value = initialValue;
this.isUpdated = false;
this.context = ctx;
Property.dependsOn[this.id] = [];
Property.isDependedOnBy[this.id] = [];
Property.idMapping[this.id] = this;
}
// Static properties on Property function.
Property.id = 0;
Property.dependsOn = {};
Property.isDependedOnBy = {};
Property.idMapping = {};
// Calling this updates all dependencies from the node outward.
Property.resolveDependencies = function (node) {
node = node.id;
var visible = [];
// Using Depth First Search to mark visibility (only want to update dependencies that are visible).
var depthFirst = function (node) {
visible.push(node);
for (var i = 0; i < Property.isDependedOnBy[node].length; i++) {
depthFirst(Property.isDependedOnBy[node][i]);
}
};
depthFirst(node);
// Topological sort to make sure updates are done in the correct order.
var generateOrder = function (inbound) {
var noIncomingEdges = [];
for (var key in inbound) {
if (inbound.hasOwnProperty(key)) {
if (inbound[key].length === 0) {
// Only call update if visible.
if (_.indexOf(visible, key) !== -1) {
Property.idMapping[key].computeValue();
}
noIncomingEdges.push(key);
delete inbound[key];
}
}
}
for (var key in inbound) {
if (inbound.hasOwnProperty(key)) {
for (var i = 0; i < noIncomingEdges.length; i++) {
inbound[key] = _.without(inbound[key], noIncomingEdges[i]);
}
}
}
// Check if the object has anymore nodes.
for (var prop in inbound) {
if (Object.prototype.hasOwnProperty.call(inbound, prop)) {
generateOrder(inbound);
}
}
};
generateOrder(_.clone(Property.dependsOn));
};
Property.prototype.get = function () {
return this.value;
}
Property.prototype.set = function (value) {
this.value = value;
}
Property.prototype.computeValue = function () {
// Call code that updates this.value.
};
Property.prototype.dependsOn = function (prop) {
Property.dependsOn[this.id].push(prop.id);
Property.isDependedOnBy[prop.id].push(this.id);
}
function PropertyFactory(methodObject) {
var self = this;
var PropType = function (initialValue) {
Property.call(this, initialValue, self);
}
PropType.prototype = Object.create(Property.prototype);
PropType.prototype.constructor = PropType;
if (methodObject.get !== null) {
PropType.prototype.get = methodObject.get;
}
if (methodObject.set !== null) {
PropType.prototype.set = methodObject.set;
}
if (methodObject.computeValue !== null) {
PropType.prototype.computeValue = methodObject.computeValue;
}
return new PropType(methodObject.initialValue);
}
And here is an example of what setting up a property looks like:
function MyClassContainer() {
this.children = [];
this.prop = PropertyFactory.call(this, {
initialValue: 0,
get: null,
set: null,
computeValue: function () {
var self = this.context;
var updatedVal = self.children[0].prop.get() + self.children[1].prop.get();
this.set(updatedVal);
}
});
}
MyClassContainer.prototype.addChildren = function (child) {
if (this.children.length === 0 || this.children.length === 1) {
// Here is the key line. This line is setting up the dependency between
// object properties.
this.prop.dependsOn(child.prop);
}
this.children.push(child);
}
function MyClass() {
this.prop = PropertyFactory.call(this, {
initialValue: 5,
get: null,
set: null,
computeValue: null
});
}
var c = new MyClassContainer();
var c1 = new MyClass();
var c2 = new MyClass();
c.addChildren(c1);
c.addChildren(c2);
And here is an example of actually updating a property once all of this infrastructure is set up:
c1.prop.set(3);
Property.resolveDependencies(c1.prop);
I feel like this is a pretty powerful pattern for programs that require really complicated dependencies. Knockout JS has something similar, with computedObservables (and they use a wrapper in a similar fashion), but you can only tie the computed property to other properties on the same object from what I can tell. The above pattern allows you to arbitrarily associate object properties as dependencies.

disabling imageSmoothingEnabled by default on multiple canvases

I'm creating a browser-based game that uses layered canvases and sprite images, and for visual and performance reasons I would like to disable imageSmoothingEnabled by default. It's my understanding that imageSmoothingEnabled isn't available in all browsers, but there are vendor-prefixed versions. I am trying to find an elegant way to disable this attribute by default across all my canvases (in as many browsers as possible). So far, this is my method:
context1.imageSmoothingEnabled = false;
context1.mozImageSmoothingEnabled = false;
context1.oImageSmoothingEnabled = false;
context1.webkitImageSmoothingEnabled = false;
context2.imageSmoothingEnabled = false;
context2.mozImageSmoothingEnabled = false;
context2.oImageSmoothingEnabled = false;
context2.webkitImageSmoothingEnabled = false;
context3.imageSmoothingEnabled = false;
context3.mozImageSmoothingEnabled = false;
context3.oImageSmoothingEnabled = false;
context3.webkitImageSmoothingEnabled = false;
//etc...
Is there a more elegant approach? Is it perhaps possible to change the context's API to default to false, before actually creating each canvas context?
Yes, you have a cleaner approach : since you will always get a context by using getContext('2d') on a canvas, you can inject getContext, so that it does any setup of your like before returning the context.
The following piece of code successfully sets the smoothing to false for all your contexts :
(it should, quite obviously, be run before any call to getContext).
// save old getContext
var oldgetContext = HTMLCanvasElement.prototype.getContext ;
// get a context, set it to smoothed if it was a 2d context, and return it.
function getSmoothContext(contextType) {
var resCtx = oldgetContext.apply(this, arguments);
if (contextType == '2d') {
setToFalse(resCtx, 'imageSmoothingEnabled');
setToFalse(resCtx, 'mozImageSmoothingEnabled');
setToFalse(resCtx, 'oImageSmoothingEnabled');
setToFalse(resCtx, 'webkitImageSmoothingEnabled');
}
return resCtx ;
}
function setToFalse(obj, prop) { if ( obj[prop] !== undefined ) obj[prop] = false; }
// inject new smoothed getContext
HTMLCanvasElement.prototype.getContext = getSmoothContext ;
Rq that you can do anything in 'your' getContext. I use it to copy canvas's width, height on the context to have them at hand with no DOM access, among other things.
You can put those into a method like:
function imageSmoothingEnabled(ctx, state) {
ctx.mozImageSmoothingEnabled = state;
ctx.oImageSmoothingEnabled = state;
ctx.webkitImageSmoothingEnabled = state;
ctx.imageSmoothingEnabled = state;
}
then call for each context:
imageSmoothingEnabled(context1, false);
imageSmoothingEnabled(context2, false);
imageSmoothingEnabled(context3, false);
As these are properties you can't simply alter their defaults. The method here is pretty clean - it can be cleaner by checking the existence of the property first:
if (typeof ctx.webkitImageSmoothingEnabled !== 'undefined')
ctx.webkitImageSmoothingEnabled = state;
etc.

How to know if a canvas is a normal canvas or a webgl canvas

How to know if a canvas is running a "WebGL" or just normal canvas?
from inspecting the source, I find it a canvas in either case.
That really depends and how you want to go about finding out.
For example you could call `getContext' like this
if (someCanvas.getContext("2d")) {
// It's a 2D canvas
} else if (someCanvas.getContext("experimental-webgl") ||
someCanvas.getContext("webgl")) {
// It's a WebGL canvas
}
Unfortunately that will also make the canvas a 2D canvas if no one has previously called getContext.
Another option is you could wrap getContext something like this
(function() {
var originalGetContextFunction = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function() {
var type = arguments[0];
var ctx = originalGetContextFunction.apply(this, arguments);
if (ctx && !this.canvasType) {
this.canvasType = type;
}
return ctx;
};
}());
Now for any canvas you can check someCanvas.canvasType to find out what string was passed into getContext. Example:
var c1 = document.createElement("canvas");
var c2 = document.createElement("canvas");
var ctx = c1.getContext("2d");
var gl = c2.getContext("experimental-webgl");
alert("c1 is " + c1.canvasType + ", c2 is " + c2.canvasType);
Just make sure the wrapper is included before any code that creates canvases.
Canvas doesn't run anything - it is just an element with a modifiable bitmap in both cases. It cannot, by itself, tell what it is being used for.
Canvas can supply you with contexts though which can be either. If you for some reason is not able to see in the source code what context is being requested you can always test the context (a bit backward but here goes):
if (ctx === null) {
return 'unsupported';
}
else if (typeof ctx.viewport === 'undefined' && // test some existing non-shared
typeof ctx.arc !== 'undefined') { // method names
return '2d';
}
else if (typeof ctx.viewport !== 'undefined' &&
typeof ctx.arc === 'undefined') {
return 'webgl';
}
else {
return 'unknown';
}
Testing for WebGLRenderingContext will not help as this test support in the browser. It is not given that you will get a WebGL context even if the browser supports WebGL as this is dependent on the computer and its hardware-capability as well.
if you do canvas.getContext('2d') and it return null its not a 2d context, same for the other context types. If on the other hand the context is not yet created, it will create the context.

Categories

Resources