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 7 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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Change Log

##### Additions :tada:
* `czm_materialInput.aspect` was added as an angle in radians between 0 and 2pi (east, north, west to south).
* Add support for decoding images while fetching using `createImageBitmap` to greatly speed up texture upload and decrease frame drops when loading models with large textures. `Resource` now has a `flipImage` option to vertically flip an image during fetch & decode when `ImageBitmapOptions` are supported. [#7579](https://github.com/AnalyticalGraphicsInc/cesium/pull/7579)

##### Fixes :wrench:
* Fixed an issue where models would cause a crash on load if some primitives were Draco encoded and others were not. [#7383](https://github.com/AnalyticalGraphicsInc/cesium/issues/7383)
Expand Down
71 changes: 69 additions & 2 deletions Source/Core/FeatureDetection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ define([
'./defaultValue',
'./defined',
'./Fullscreen',
'./RuntimeError',
'../ThirdParty/when'
], function(
defaultValue,
defined,
Fullscreen,
RuntimeError,
when) {
'use strict';
/*global CanvasPixelArray*/
Expand Down Expand Up @@ -255,6 +253,71 @@ define([
}
}

var supportsCreateImageBitmapResult;
function supportsCreateImageBitmap() {
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
if (!defined(supportsCreateImageBitmapResult)) {
supportsCreateImageBitmapResult = defined(window.createImageBitmap);
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
}

return supportsCreateImageBitmapResult;
}

var supportsImageBitmapOptionsResult;
var supportsImageBitmapOptionsPromise;
function supportsImageBitmapOptions() {
// 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.promise;
}

supportsImageBitmapOptionsPromise = when.defer();

if (!supportsCreateImageBitmap()) {
supportsImageBitmapOptionsResult = false;
supportsImageBitmapOptionsPromise.resolve(supportsImageBitmapOptionsResult);
return supportsImageBitmapOptionsPromise.promise;
}

var imageDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWP4////fwAJ+wP9CNHoHgAAAABJRU5ErkJggg==';
fetch(imageDataUri)
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved
.then(function(response) {
return response.blob();
})
.then(function(blob) {
return createImageBitmap(blob, {
imageOrientation: 'flipY'
});
})
.then(function(imageBitmap) {
supportsImageBitmapOptionsResult = true;
supportsImageBitmapOptionsPromise.resolve(supportsImageBitmapOptionsResult);
})
.catch(function() {
supportsImageBitmapOptionsResult = false;
supportsImageBitmapOptionsPromise.resolve(supportsImageBitmapOptionsResult);
});

return supportsImageBitmapOptionsPromise.promise;
}

function supportsImageBitmapOptionsSync() {
if (!defined(supportsImageBitmapOptionsPromise)) {
supportsImageBitmapOptions();
}

return supportsImageBitmapOptionsResult;
}

var supportsFetchApiResult;
function supportsFetchApi() {
if (!defined(supportsFetchApiResult)) {
supportsFetchApiResult = defined(window.fetch);
}
return supportsFetchApiResult;
}

/**
* A set of functions to detect whether the current browser supports
* various features.
Expand All @@ -280,6 +343,10 @@ define([
supportsImageRenderingPixelated: supportsImageRenderingPixelated,
supportsWebP: supportsWebP,
supportsWebPSync: supportsWebPSync,
supportsCreateImageBitmap: supportsCreateImageBitmap,
supportsImageBitmapOptions: supportsImageBitmapOptions,
supportsImageBitmapOptionsSync: supportsImageBitmapOptionsSync,
supportsFetchApi : supportsFetchApi,
imageRenderingValue: imageRenderingValue,
typedArrayTypes: typedArrayTypes
};
Expand Down
83 changes: 65 additions & 18 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 @@ -250,6 +252,7 @@ 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.flipImage] 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.
Expand Down Expand Up @@ -336,6 +339,14 @@ define([
this.retryAttempts = defaultValue(options.retryAttempts, 0);
this._retryCount = 0;

/**
* Whether to vertically flip the image during fetch and decode. Only applies when requesting
* an image and the browser supports createImageBitmap.
*
* @type {Boolean}
*/
this.flipImage = defaultValue(options.flipImage, true);
OmarShehata marked this conversation as resolved.
Show resolved Hide resolved

var uri = new Uri(options.url);
parseQuery(uri, this, true, true);

Expand Down Expand Up @@ -620,6 +631,7 @@ define([
* @param {Object} [options.templateValues] Key/Value pairs that are used to replace template values (eg. {x}). These will be combined with those of the current instance.
* @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.flipImage] 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 loading the resource fails.
* @param {Number} [options.retryAttempts] 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.
Expand Down Expand Up @@ -664,6 +676,9 @@ define([
if (defined(options.retryAttempts)) {
resource.retryAttempts = options.retryAttempts;
}
if (defined(options.flipImage)) {
resource.flipImage = options.flipImage;
}

return resource;
};
Expand Down Expand Up @@ -714,6 +729,7 @@ define([
result.retryCallback = this.retryCallback;
result.retryAttempts = this.retryAttempts;
result._retryCount = 0;
result.flipImage = this.flipImage;
result.request = this.request.clone();

return result;
Expand Down Expand Up @@ -771,6 +787,7 @@ 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.flipImage] 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.
Expand Down Expand Up @@ -815,6 +832,7 @@ 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.flipImage] 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.
Expand Down Expand Up @@ -851,6 +869,7 @@ define([
*/
Resource.prototype.fetchImage = function (preferBlob) {
preferBlob = defaultValue(preferBlob, false);
var flipImage = this.flipImage;

checkAndResetRequest(this.request);

Expand All @@ -860,7 +879,7 @@ 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);
}

var blobPromise = this.fetchBlob();
Expand All @@ -878,7 +897,8 @@ define([
generatedBlob = blob;
var blobUrl = window.URL.createObjectURL(blob);
generatedBlobResource = new Resource({
url: blobUrl
url: blobUrl,
flipImage: flipImage
});

return fetchImage(generatedBlobResource);
Expand Down Expand Up @@ -908,6 +928,7 @@ define([
request.url = resource.url;
request.requestFunction = function() {
var url = resource.url;
var flipImage = resource.flipImage;
var crossOrigin = false;

// data URIs can't have crossorigin set.
Expand All @@ -917,7 +938,7 @@ define([

var deferred = when.defer();

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

return deferred.promise;
};
Expand Down Expand Up @@ -958,6 +979,7 @@ 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.flipImage] 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.
Expand Down Expand Up @@ -1378,6 +1400,7 @@ 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.flipImage] 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.
Expand Down Expand Up @@ -1755,26 +1778,50 @@ define([
*/
Resource._Implementations = {};

Resource._Implementations.createImage = function(url, crossOrigin, deferred) {
var image = new Image();
Resource._Implementations.createImage = function(url, crossOrigin, deferred, flipImage) {
var supportsBitmapOptions = FeatureDetection.supportsImageBitmapOptionsSync();

image.onload = function() {
deferred.resolve(image);
};
if (FeatureDetection.supportsFetchApi() && FeatureDetection.supportsCreateImageBitmap() && defined(supportsBitmapOptions)) {
fetch(url, {
credentials: (crossOrigin && TrustedServers.contains(url)) ? 'include' : 'same-origin'
})
.then(function(response) {
return response.blob();
})
.then(function(blob) {
if (!supportsBitmapOptions) {
return createImageBitmap(blob);
}

image.onerror = function(e) {
deferred.reject(e);
};
return createImageBitmap(blob, {
imageOrientation: flipImage ? 'flipY' : 'none'
});
})
.then(function (imageBitmap) {
deferred.resolve(imageBitmap);
})
.catch(deferred.reject);
} else {
var image = new Image();

if (crossOrigin) {
if (TrustedServers.contains(url)) {
image.crossOrigin = 'use-credentials';
} else {
image.crossOrigin = '';
image.onload = function() {
deferred.resolve(image);
};

image.onerror = function(e) {
deferred.reject(e);
};

if (crossOrigin) {
if (TrustedServers.contains(url)) {
image.crossOrigin = 'use-credentials';
} else {
image.crossOrigin = '';
}
}
}

image.src = url;
image.src = url;
}
};

function decodeResponse(loadWithHttpResponse, responseType) {
Expand Down
25 changes: 24 additions & 1 deletion 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 @@ -21,6 +31,19 @@ define([
type : format
});

// We can avoid the extra fetch when createImageBitmap is supported.
var supportsBitmapOptions = FeatureDetection.supportsImageBitmapOptionsSync();

if (FeatureDetection.supportsCreateImageBitmap() && defined(supportsBitmapOptions)) {
if (supportsBitmapOptions) {
return when(createImageBitmap(blob, {
imageOrientation: flipY ? 'flipY' : 'none'
}));
}

return when(createImageBitmap(blob));
}

var blobUrl = window.URL.createObjectURL(blob);
var resource = new Resource({
url: blobUrl,
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
9 changes: 7 additions & 2 deletions Source/Scene/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -1714,7 +1714,8 @@ define([
++model._loadResources.pendingTextureLoads;

var imageResource = model._resource.getDerivedResource({
url : uri
url : uri,
flipImage : false
});

var promise;
Expand Down Expand Up @@ -2325,7 +2326,11 @@ define([
++model._loadResources.pendingTextureLoads;
} else {
var onload = getOnImageCreatedFromTypedArray(loadResources, gltfTexture);
loadImageFromTypedArray(loadResources.getBuffer(bufferView), gltfTexture.mimeType)
loadImageFromTypedArray({
uint8Array: loadResources.getBuffer(bufferView),
format: gltfTexture.mimeType,
flipY: false
})
.then(onload).otherwise(onerror);
++loadResources.pendingBufferViewToImage;
}
Expand Down
Loading