Skip to content

Commit

Permalink
Improve detection of transferring array buffers.
Browse files Browse the repository at this point in the history
Remove FeatureDetection.supportsTransferringArrayBuffers which incorrectly used window.postMessage.

Detection is now done internally inside TaskProcessor using a real web worker, which round-trips a typed array to detect a related bug in older Firefox versions.  TaskProcessor sends the result of the detection to the worker along with the task object, instead of having the worker detect itself.

Instead of detecting whether or not ArrayBuffers can be transferred everywhere in Cesium code, we now assume they can, and the TaskProcessor/createTaskProcessorWorker system will internally truncate the array if it's not supported.
  • Loading branch information
shunter committed Apr 7, 2014
1 parent a7cd966 commit a3a6aeb
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 210 deletions.
29 changes: 1 addition & 28 deletions Source/Core/FeatureDetection.js
Expand Up @@ -9,7 +9,7 @@ define([

function extractVersion(versionString) {
var parts = versionString.split('.');
for ( var i = 0, len = parts.length; i < len; ++i) {
for (var i = 0, len = parts.length; i < len; ++i) {
parts[i] = parseInt(parts[i], 10);
}
return parts;
Expand Down Expand Up @@ -149,32 +149,5 @@ define([
return typeof ArrayBuffer !== 'undefined';
};

var supportsTransferringArrayBuffersResult;

/**
* Detects whether the current browser can transfer an ArrayBuffer
* to / from a web worker.
*
* @returns true if the browser can transfer ArrayBuffers; otherwise, false.
*/
FeatureDetection.supportsTransferringArrayBuffers = function() {
if (!defined(supportsTransferringArrayBuffersResult)) {
if (!FeatureDetection.supportsTypedArrays()) {
supportsTransferringArrayBuffersResult = false;
return;
}

var arrayBuffer = new ArrayBuffer(1);
try {
/*global postMessage*/
postMessage({ value : arrayBuffer }, [arrayBuffer]);
supportsTransferringArrayBuffersResult = true;
} catch(e) {
supportsTransferringArrayBuffersResult = false;
}
}
return supportsTransferringArrayBuffersResult;
};

return FeatureDetection;
});
109 changes: 78 additions & 31 deletions Source/Core/TaskProcessor.js
Expand Up @@ -19,12 +19,51 @@ define([
Uri) {
"use strict";

function completeTask(processor, event) {
function canTransferArrayBuffer() {
if (!defined(TaskProcessor._canTransferArrayBuffer)) {
var worker = new Worker(getWorkerUrl('Workers/transferTypedArrayTest.js'));
worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage);

var value = 99;
var array = new Int8Array([value]);

try {
// postMessage might fail with a DataCloneError
// if transferring array buffers is not supported.
worker.postMessage({
array : array
}, [array.buffer]);
} catch (e) {
TaskProcessor._canTransferArrayBuffer = false;
return TaskProcessor._canTransferArrayBuffer;
}

var deferred = when.defer();

worker.onmessage = function(event) {
var array = event.data.array;

// some versions of Firefox silently fail to transfer typed arrays.
// https://bugzilla.mozilla.org/show_bug.cgi?id=841904
// Check to make sure the value round-trips successfully.
var result = defined(array) && array[0] === value;
deferred.resolve(result);

worker.terminate();

TaskProcessor._canTransferArrayBuffer = result;
};

TaskProcessor._canTransferArrayBuffer = deferred.promise;
}

return TaskProcessor._canTransferArrayBuffer;
}

function completeTask(processor, data) {
--processor._activeTasks;

var data = event.data;
var id = data.id;

if (!defined(id)) {
// This is not one of ours.
return;
Expand All @@ -42,17 +81,12 @@ define([
delete deferreds[id];
}

var _bootstrapperUrl;
function getBootstrapperUrl() {
if (defined(_bootstrapperUrl)) {
return _bootstrapperUrl;
}

_bootstrapperUrl = buildModuleUrl('Workers/cesiumWorkerBootstrapper.js');
function getWorkerUrl(moduleID) {
var url = buildModuleUrl(moduleID);

if (isCrossOriginUrl(_bootstrapperUrl)) {
if (isCrossOriginUrl(url)) {
//to load cross-origin, create a shim worker from a blob URL
var script = 'importScripts("' + _bootstrapperUrl + '");';
var script = 'importScripts("' + url + '");';

var blob;
try {
Expand All @@ -67,18 +101,24 @@ define([
}

var URL = window.URL || window.webkitURL;
_bootstrapperUrl = URL.createObjectURL(blob);
url = URL.createObjectURL(blob);
}

return _bootstrapperUrl;
return url;
}

var bootstrapperUrlResult;
function getBootstrapperUrl() {
if (!defined(bootstrapperUrlResult)) {
bootstrapperUrlResult = getWorkerUrl('Workers/cesiumWorkerBootstrapper.js');
}
return bootstrapperUrlResult;
}

function createWorker(processor) {
var bootstrapperUrl = getBootstrapperUrl();
var worker = new Worker(bootstrapperUrl);
var worker = new Worker(getBootstrapperUrl());
worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage);

//bootstrap
var bootstrapMessage = {
loaderConfig : {},
workerModule : TaskProcessor._workerModulePrefix + processor._workerName
Expand All @@ -98,10 +138,10 @@ define([
worker.postMessage(bootstrapMessage);

worker.onmessage = function(event) {
completeTask(processor, event);
completeTask(processor, event.data);
};

processor._worker = worker;
return worker;
}

/**
Expand Down Expand Up @@ -157,7 +197,7 @@ define([
*/
TaskProcessor.prototype.scheduleTask = function(parameters, transferableObjects) {
if (!defined(this._worker)) {
createWorker(this);
this._worker = createWorker(this);
}

if (this._activeTasks >= this._maximumActiveTasks) {
Expand All @@ -166,20 +206,26 @@ define([

++this._activeTasks;

if (!defined(transferableObjects)) {
transferableObjects = emptyTransferableObjectArray;
}
var processor = this;
return when(canTransferArrayBuffer(), function(canTransferArrayBuffer) {
if (!defined(transferableObjects)) {
transferableObjects = emptyTransferableObjectArray;
} else if (!canTransferArrayBuffer) {
transferableObjects.length = 0;
}

var id = this._nextID++;
var deferred = when.defer();
this._deferreds[id] = deferred;
var id = processor._nextID++;
var deferred = when.defer();
processor._deferreds[id] = deferred;

this._worker.postMessage({
id : id,
parameters : parameters
}, transferableObjects);
processor._worker.postMessage({
id : id,
parameters : parameters,
canTransferArrayBuffer : canTransferArrayBuffer
}, transferableObjects);

return deferred.promise;
return deferred.promise;
});
};

/**
Expand Down Expand Up @@ -219,6 +265,7 @@ define([
TaskProcessor._defaultWorkerModulePrefix = 'Workers/';
TaskProcessor._workerModulePrefix = TaskProcessor._defaultWorkerModulePrefix;
TaskProcessor._loaderConfig = undefined;
TaskProcessor._canTransferArrayBuffer = undefined;

return TaskProcessor;
});
3 changes: 0 additions & 3 deletions Source/Scene/Primitive.js
Expand Up @@ -622,9 +622,6 @@ define([
this._state = PrimitiveState.COMBINING;

when(promise, function(result) {
PrimitivePipeline.receiveGeometries(result.geometries);
PrimitivePipeline.receivePerInstanceAttributes(result.vaAttributes);

that._geometries = result.geometries;
that._attributeLocations = result.attributeLocations;
that._vaAttributes = result.vaAttributes;
Expand Down
126 changes: 8 additions & 118 deletions Source/Scene/PrimitivePipeline.js
Expand Up @@ -386,75 +386,23 @@ define([
};
};

/*
* The below functions are needed when transferring typed arrays to/from web
* workers. This is a workaround for:
*
* https://bugzilla.mozilla.org/show_bug.cgi?id=841904
*/

function stupefyTypedArray(typedArray) {
if (defined(typedArray.constructor.name)) {
return {
type : typedArray.constructor.name,
buffer : typedArray.buffer
};
} else {
return typedArray;
}
}

var typedArrayMap = {
Int8Array : Int8Array,
Uint8Array : Uint8Array,
Int16Array : Int16Array,
Uint16Array : Uint16Array,
Int32Array : Int32Array,
Uint32Array : Uint32Array,
Float32Array : Float32Array,
Float64Array : Float64Array
};

function unStupefyTypedArray(typedArray) {
if (defined(typedArray.type)) {
return new typedArrayMap[typedArray.type](typedArray.buffer);
} else {
return typedArray;
}
}

/**
* @private
*/
PrimitivePipeline.transferGeometry = function(geometry, transferableObjects) {
var typedArray;
var attributes = geometry.attributes;
for (var name in attributes) {
if (attributes.hasOwnProperty(name) &&
defined(attributes[name]) &&
defined(attributes[name].values)) {
typedArray = attributes[name].values;

if (FeatureDetection.supportsTransferringArrayBuffers() && transferableObjects.indexOf(attributes[name].values.buffer) < 0) {
transferableObjects.push(typedArray.buffer);
}
for ( var name in attributes) {
if (attributes.hasOwnProperty(name)) {
var attribute = attributes[name];

if (!defined(typedArray.type)) {
attributes[name].values = stupefyTypedArray(typedArray);
if (defined(attribute) && defined(attribute.values)) {
transferableObjects.push(attribute.values.buffer);
}
}
}

if (defined(geometry.indices)) {
typedArray = geometry.indices;

if (FeatureDetection.supportsTransferringArrayBuffers()) {
transferableObjects.push(typedArray.buffer);
}

if (!defined(typedArray.type)) {
geometry.indices = stupefyTypedArray(geometry.indices);
}
transferableObjects.push(geometry.indices.buffer);
}
};

Expand All @@ -477,11 +425,7 @@ define([
var vaAttributes = perInstanceAttributes[i];
var vaLength = vaAttributes.length;
for (var j = 0; j < vaLength; ++j) {
var typedArray = vaAttributes[j].values;
if (FeatureDetection.supportsTransferringArrayBuffers()) {
transferableObjects.push(typedArray.buffer);
}
vaAttributes[j].values = stupefyTypedArray(typedArray);
transferableObjects.push(vaAttributes[j].values.buffer);
}
}
};
Expand All @@ -492,61 +436,7 @@ define([
PrimitivePipeline.transferInstances = function(instances, transferableObjects) {
var length = instances.length;
for (var i = 0; i < length; ++i) {
var instance = instances[i];
PrimitivePipeline.transferGeometry(instance.geometry, transferableObjects);
}
};

/**
* @private
*/
PrimitivePipeline.receiveGeometry = function(geometry) {
var attributes = geometry.attributes;
for (var name in attributes) {
if (attributes.hasOwnProperty(name) &&
defined(attributes[name]) &&
defined(attributes[name].values)) {
attributes[name].values = unStupefyTypedArray(attributes[name].values);
}
}

if (defined(geometry.indices)) {
geometry.indices = unStupefyTypedArray(geometry.indices);
}
};

/**
* @private
*/
PrimitivePipeline.receiveGeometries = function(geometries) {
var length = geometries.length;
for (var i = 0; i < length; ++i) {
PrimitivePipeline.receiveGeometry(geometries[i]);
}
};

/**
* @private
*/
PrimitivePipeline.receivePerInstanceAttributes = function(perInstanceAttributes) {
var length = perInstanceAttributes.length;
for (var i = 0; i < length; ++i) {
var vaAttributes = perInstanceAttributes[i];
var vaLength = vaAttributes.length;
for (var j = 0; j < vaLength; ++j) {
vaAttributes[j].values = unStupefyTypedArray(vaAttributes[j].values);
}
}
};

/**
* @private
*/
PrimitivePipeline.receiveInstances = function(instances) {
var length = instances.length;
for (var i = 0; i < length; ++i) {
var instance = instances[i];
PrimitivePipeline.receiveGeometry(instance.geometry);
PrimitivePipeline.transferGeometry(instances[i].geometry, transferableObjects);
}
};

Expand Down
1 change: 0 additions & 1 deletion Source/Workers/combineGeometry.js
Expand Up @@ -20,7 +20,6 @@ define([
parameters.projection = parameters.isGeographic ? new GeographicProjection(parameters.ellipsoid) : new WebMercatorProjection(parameters.ellipsoid);
parameters.modelMatrix = Matrix4.clone(parameters.modelMatrix);

PrimitivePipeline.receiveInstances(parameters.instances);
var result = PrimitivePipeline.combineGeometry(parameters);
PrimitivePipeline.transferGeometries(result.geometries, transferableObjects);
PrimitivePipeline.transferPerInstanceAttributes(result.vaAttributes, transferableObjects);
Expand Down

0 comments on commit a3a6aeb

Please sign in to comment.