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

Use createImageBitmap when available #7579

Merged
merged 20 commits into from
Mar 10, 2019
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
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ Change Log

### 1.56 - 2019-04-01

##### Breaking Changes :mega:
* `Resource.fetchImage` now returns an `ImageBitmap` instead of `Image` when supported. This allows for decoding images while fetching using `createImageBitmap` to greatly speed up texture upload and decrease frame drops when loading models with large textures. [#7579](https://github.com/AnalyticalGraphicsInc/cesium/pull/7579)

##### Deprecated :hourglass_flowing_sand:
* `Resource.fetchImage` now takes an options object. Use `resource.fetchImage({ preferBlob: true })` instead of `resource.fetchImage(true)`. The previous function definition will no longer work in 1.57. [#7579](https://github.com/AnalyticalGraphicsInc/cesium/pull/7579)

##### Additions :tada:
* `Resource.fetchImage` now has a `flipY` option to vertically flip an image during fetch & decode. It is only valid when `ImageBitmapOptions` is supported by the browser. [#7579](https://github.com/AnalyticalGraphicsInc/cesium/pull/7579)

##### Fixes :wrench:
* Fixed the value for `BlendFunction.ONE_MINUS_CONSTANT_COLOR`. [#7624](https://github.com/AnalyticalGraphicsInc/cesium/pull/7624)

Expand Down
2 changes: 0 additions & 2 deletions Source/Core/FeatureDetection.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ define([
'./defineProperties',
'./DeveloperError',
'./Fullscreen',
'./RuntimeError',
'../ThirdParty/when'
], function(
defaultValue,
defined,
defineProperties,
DeveloperError,
Fullscreen,
RuntimeError,
when) {
'use strict';
/*global CanvasPixelArray*/
Expand Down
14 changes: 12 additions & 2 deletions Source/Core/IonResource.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,18 @@ define([
return result;
};

IonResource.prototype.fetchImage = function (preferBlob, allowCrossOrigin) {
return Resource.prototype.fetchImage.call(this, this._isExternal ? preferBlob : true, allowCrossOrigin);
IonResource.prototype.fetchImage = function (options) {
if (!this._isExternal) {
var userOptions = options;
options = {
preferBlob : true
};
if (defined(userOptions)) {
options.flipY = userOptions.flipY;
}
}

return Resource.prototype.fetchImage.call(this, options);
};

IonResource.prototype._makeRequest = function(options) {
Expand Down
146 changes: 132 additions & 14 deletions Source/Core/Resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ define([
'./deprecationWarning',
'./DeveloperError',
'./freezeObject',
'./FeatureDetection',
'./getAbsoluteUri',
'./getBaseUri',
'./getExtensionFromUri',
Expand Down Expand Up @@ -39,6 +40,7 @@ define([
deprecationWarning,
DeveloperError,
freezeObject,
FeatureDetection,
getAbsoluteUri,
getBaseUri,
getExtensionFromUri,
Expand Down Expand Up @@ -374,6 +376,47 @@ define([
});
};

var supportsImageBitmapOptionsPromise;
/**
* A helper function to check whether createImageBitmap supports passing ImageBitmapOptions.
*
* @returns {Promise<Boolean>} A promise that resolves to true if this browser supports creating an ImageBitmap with options.
*
* @private
*/
Resource.supportsImageBitmapOptions = function() {
// Until the HTML folks figure out what to do about this, we need to actually try loading an image to
// know if this browser supports passing options to the createImageBitmap function.
// https://github.com/whatwg/html/pull/4248
if (defined(supportsImageBitmapOptionsPromise)) {
return supportsImageBitmapOptionsPromise;
}

if (typeof createImageBitmap !== 'function') {
supportsImageBitmapOptionsPromise = when.resolve(false);
return supportsImageBitmapOptionsPromise;
}

var imageDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==';

supportsImageBitmapOptionsPromise = Resource.fetchBlob({
url : imageDataUri
})
.then(function(blob) {
return createImageBitmap(blob, {
imageOrientation: 'flipY'
});
})
.then(function(imageBitmap) {
return true;
})
.otherwise(function() {
return false;
});

return supportsImageBitmapOptionsPromise;
};

defineProperties(Resource, {
/**
* Returns true if blobs are supported.
Expand Down Expand Up @@ -827,10 +870,13 @@ define([

/**
* Asynchronously loads the given image resource. Returns a promise that will resolve to
* an {@link Image} once loaded, or reject if the image failed to load.
* an {@link https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap|ImageBitmap} if the browser supports `createImageBitmap` or otherwise an
* {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement|Image} once loaded, or reject if the image failed to load.
*
* @param {Boolean} [preferBlob = false] If true, we will load the image via a blob.
* @returns {Promise.<Image>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
* @param {Object} [options] An object with the following properties.
* @param {Boolean} [options.preferBlob=false] If true, we will load the image via a blob.
* @param {Boolean} [options.flipY=true] If true, image will be vertially flipped during decode. Only applies if the browser supports `createImageBitmap`.
* @returns {Promise.<ImageBitmap>|Promise.<Image>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
*
*
* @example
Expand All @@ -849,8 +895,16 @@ define([
* @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
* @see {@link http://wiki.commonjs.org/wiki/Promises/A|CommonJS Promises/A}
*/
Resource.prototype.fetchImage = function (preferBlob) {
preferBlob = defaultValue(preferBlob, false);
Resource.prototype.fetchImage = function (options) {
if (typeof options === 'boolean') {
deprecationWarning('fetchImage-parameter-change', 'fetchImage now takes an options object in CesiumJS 1.56. Use resource.fetchImage({ preferBlob: true }) instead of resource.fetchImage(true).');
options = {
preferBlob : options
};
}
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var preferBlob = defaultValue(options.preferBlob, false);
var flipY = defaultValue(options.flipY, true);

checkAndResetRequest(this.request);

Expand All @@ -860,33 +914,44 @@ define([
// 3. It's a blob URI
// 4. It doesn't have request headers and we preferBlob is false
if (!xhrBlobSupported || this.isDataUri || this.isBlobUri || (!this.hasHeaders && !preferBlob)) {
return fetchImage(this, true);
return fetchImage(this, flipY);
}

var blobPromise = this.fetchBlob();
if (!defined(blobPromise)) {
return;
}

var supportsImageBitmap;
var generatedBlobResource;
var generatedBlob;
return blobPromise
return Resource.supportsImageBitmapOptions()
.then(function(result) {
supportsImageBitmap = result;
return blobPromise;
})
.then(function(blob) {
if (!defined(blob)) {
return;
}
if (supportsImageBitmap) {
return Resource._Implementations.createImageBitmapFromBlob(blob, flipY);
}
generatedBlob = blob;
var blobUrl = window.URL.createObjectURL(blob);
generatedBlobResource = new Resource({
url: blobUrl
});

return fetchImage(generatedBlobResource);
return fetchImage(generatedBlobResource, flipY);
})
.then(function(image) {
if (!defined(image)) {
return;
}
if (supportsImageBitmap) {
return image;
}
window.URL.revokeObjectURL(generatedBlobResource.url);

// This is because the blob object is needed for DiscardMissingTileImagePolicy
Expand All @@ -903,7 +968,7 @@ define([
});
};

function fetchImage(resource) {
function fetchImage(resource, flipY) {
var request = resource.request;
request.url = resource.url;
request.requestFunction = function() {
Expand All @@ -917,7 +982,7 @@ define([

var deferred = when.defer();

Resource._Implementations.createImage(url, crossOrigin, deferred);
Resource._Implementations.createImage(url, crossOrigin, deferred, flipY);

return deferred.promise;
};
Expand All @@ -941,7 +1006,7 @@ define([
request.state = RequestState.UNISSUED;
request.deferred = undefined;

return fetchImage(resource);
return fetchImage(resource, flipY);
}

return when.reject(e);
Expand All @@ -958,15 +1023,19 @@ define([
* @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}).
* @param {Object} [options.headers={}] Additional HTTP headers that will be sent.
* @param {DefaultProxy} [options.proxy] A proxy to be used when loading the resource.
* @param {Boolean} [options.flipY = true] Whether to vertically flip the image during fetch and decode. Only applies when requesting an image and the browser supports createImageBitmap.
* @param {Resource~RetryCallback} [options.retryCallback] The Function to call when a request for this resource fails. If it returns true, the request will be retried.
* @param {Number} [options.retryAttempts=0] The number of times the retryCallback should be called before giving up.
* @param {Request} [options.request] A Request object that will be used. Intended for internal use only.
* @param {Boolean} [options.preferBlob = false] If true, we will load the image via a blob.
* @returns {Promise.<Image>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
* @returns {Promise.<ImageBitmap>|Promise.<Image>|undefined} a promise that will resolve to the requested data when loaded. Returns undefined if <code>request.throttle</code> is true and the request does not have high enough priority.
*/
Resource.fetchImage = function (options) {
var resource = new Resource(options);
return resource.fetchImage(options.preferBlob);
return resource.fetchImage({
flipY: options.flipY,
preferBlob: options.preferBlob
});
};

/**
Expand Down Expand Up @@ -1755,7 +1824,7 @@ define([
*/
Resource._Implementations = {};

Resource._Implementations.createImage = function(url, crossOrigin, deferred) {
function loadImageElement(url, crossOrigin, deferred) {
var image = new Image();

image.onload = function() {
Expand All @@ -1775,6 +1844,55 @@ define([
}

image.src = url;
}

Resource._Implementations.createImage = function(url, crossOrigin, deferred, flipY) {
// Passing an Image to createImageBitmap will force it to run on the main thread
// since DOM elements don't exist on workers. We convert it to a blob so it's non-blocking.
// See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1044102#c38
// https://bugs.chromium.org/p/chromium/issues/detail?id=580202#c10
Resource.supportsImageBitmapOptions()
.then(function(result) {
// We can only use ImageBitmap if we can flip on decode.
// See: https://github.com/AnalyticalGraphicsInc/cesium/pull/7579#issuecomment-466146898
if (!result) {
loadImageElement(url, crossOrigin, deferred);
return;
}

return Resource.fetchBlob({
url: url
});
})
.then(function(blob) {
if (!defined(blob)) {
return;
}

return Resource._Implementations.createImageBitmapFromBlob(blob, flipY);
})
.then(function(imageBitmap) {
if (!defined(imageBitmap)) {
return;
}

deferred.resolve(imageBitmap);
})
.otherwise(deferred.reject);
};

Resource._Implementations.createImageBitmapFromBlob = function(blob, flipY) {
return Resource.supportsImageBitmapOptions()
.then(function(result) {
if (!result) {
return createImageBitmap(blob);
}

return createImageBitmap(blob, {
imageOrientation: flipY ? 'flipY' : 'none'
});
});
};

function decodeResponse(loadWithHttpResponse, responseType) {
Expand Down
19 changes: 16 additions & 3 deletions Source/Core/loadImageFromTypedArray.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
define([
'../ThirdParty/when',
'./Check',
'./defined',
'./defaultValue',
'./FeatureDetection',
'./Resource'
], function(
when,
Check,
defined,
defaultValue,
FeatureDetection,
Resource) {
'use strict';

/**
* @private
*/
function loadImageFromTypedArray(uint8Array, format, request) {
function loadImageFromTypedArray(options) {
var uint8Array = options.uint8Array;
var format = options.format;
var request = options.request;
var flipY = defaultValue(options.flipY, true);
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object('uint8Array', uint8Array);
Check.typeOf.string('format', format);
Expand All @@ -26,11 +36,14 @@ define([
url: blobUrl,
request: request
});
return resource.fetchImage()
return resource.fetchImage({
flipY : flipY
})
.then(function(image) {
window.URL.revokeObjectURL(blobUrl);
return image;
}, function(error) {
})
.otherwise(function(error) {
window.URL.revokeObjectURL(blobUrl);
return when.reject(error);
});
Expand Down
4 changes: 3 additions & 1 deletion Source/Scene/DiscardMissingTileImagePolicy.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ define([
that._isReady = true;
}

when(resource.fetchImage(true), success, failure);
resource.fetchImage({
preferBlob : true
}).then(success).otherwise(failure);
}

/**
Expand Down
5 changes: 4 additions & 1 deletion Source/Scene/GoogleEarthEnterpriseImageryProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,10 @@ define([
return invalidImage;
}

return loadImageFromTypedArray(a, type);
return loadImageFromTypedArray({
uint8Array: a,
format: type
});
});
};

Expand Down
4 changes: 3 additions & 1 deletion Source/Scene/ImageryProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,9 @@ define([
} else if (crnRegex.test(resource)) {
return loadCRN(resource);
} else if (defined(imageryProvider.tileDiscardPolicy)) {
return resource.fetchImage(true);
return resource.fetchImage({
preferBlob : true
});
}

return resource.fetchImage();
Expand Down
Loading