diff --git a/src/components/material.js b/src/components/material.js
index bd17ea0cf44..118905aecd7 100644
--- a/src/components/material.js
+++ b/src/components/material.js
@@ -250,4 +250,13 @@ function parseBlending (blending) {
function disposeMaterial (material, system) {
material.dispose();
system.unregisterMaterial(material);
+
+ // Dispose textures on this material
+ Object.keys(material)
+ .filter(function (propName) {
+ return material[propName] && material[propName].isTexture;
+ })
+ .forEach(function (mapName) {
+ material[mapName].dispose();
+ });
}
diff --git a/src/core/shader.js b/src/core/shader.js
index e28b977a558..14173943d00 100755
--- a/src/core/shader.js
+++ b/src/core/shader.js
@@ -135,9 +135,6 @@ Shader.prototype = {
color = new THREE.Color(value);
return new THREE.Vector3(color.r, color.g, color.b);
}
- case 'map': {
- return THREE.ImageUtils.loadTexture(value);
- }
default: {
return value;
}
diff --git a/src/systems/material.js b/src/systems/material.js
index 27516c0bbb8..7b902a801d7 100755
--- a/src/systems/material.js
+++ b/src/systems/material.js
@@ -2,70 +2,78 @@ var registerSystem = require('../core/system').registerSystem;
var THREE = require('../lib/three');
var utils = require('../utils/');
var setTextureProperties = require('../utils/material').setTextureProperties;
+var createCompatibleTexture = require('../utils/material').createCompatibleTexture;
var debug = utils.debug;
var error = debug('components:texture:error');
-var TextureLoader = new THREE.TextureLoader();
var warn = debug('components:texture:warn');
-
-TextureLoader.setCrossOrigin('anonymous');
+var ImageLoader = new THREE.ImageLoader();
/**
* System for material component.
* Handle material registration, updates (for fog), and texture caching.
*
* @member {object} materials - Registered materials.
- * @member {object} textureCounts - Number of times each texture is used. Tracked
- * separately from textureCache, because the cache (1) is populated in
- * multiple places, and (2) may be cleared at any time.
- * @member {object} textureCache - Texture cache for:
- * - Images: textureCache has mapping of src -> repeat -> cached three.js texture.
- * - Videos: textureCache has mapping of videoElement -> cached three.js texture.
+ * @member {object} sourceCache - Texture source cache for, Image, Video and Canvas sources
*/
module.exports.System = registerSystem('material', {
init: function () {
this.materials = {};
- this.textureCounts = {};
- this.textureCache = {};
-
- this.sceneEl.addEventListener(
- 'materialtextureloaded',
- this.onMaterialTextureLoaded.bind(this)
- );
+ this.sourceCache = {};
},
- clearTextureCache: function () {
- this.textureCache = {};
+ clearTextureSourceCache: function () {
+ this.sourceCache = {};
},
/**
- * Determine whether `src` is a image or video. Then try to load the asset, then call back.
+ * Loads and creates a texture for a given `src`.
*
- * @param {string, or element} src - Texture URL or element.
- * @param {string} data - Relevant texture data used for caching.
- * @param {function} cb - Callback to pass texture to.
+ * @param {string, or element} src - URL or element
+ * @param {object} data - Relevant texture properties
+ * @param {function} cb - Callback to pass texture to
*/
loadTexture: function (src, data, cb) {
+ this.loadTextureSource(src, function sourceLoaded (source) {
+ var texture = createCompatibleTexture(source);
+ setTextureProperties(texture, data);
+ cb(texture);
+ });
+ },
+
+ /**
+ * Determine whether `src` is an image or video. Then try to load the asset, then call back.
+ *
+ * @param {string, or element} src - URL or element.
+ * @param {function} cb - Callback to pass texture source to.
+ */
+ loadTextureSource: function (src, cb) {
var self = this;
+ var sourceCache = this.sourceCache;
+
+ var hash = this.hash(src);
+ if (sourceCache[hash]) {
+ sourceCache[hash].then(cb);
+ return;
+ }
// Canvas.
if (src.tagName === 'CANVAS') {
- this.loadCanvas(src, data, cb);
+ sourceLoaded(new THREE.Source(src));
return;
}
- // Video element.
- if (src.tagName === 'VIDEO') {
- if (!src.src && !src.srcObject && !src.childElementCount) {
- warn('Video element was defined with neither `source` elements nor `src` / `srcObject` attributes.');
- }
- this.loadVideo(src, data, cb);
- return;
+ sourceLoaded(new Promise(doSourceLoad));
+ function doSourceLoad (resolve, reject) {
+ utils.srcLoader.validateSrc(src, loadImageCb, loadVideoCb);
+ function loadImageCb (src) { self.loadImage(src, resolve); }
+ function loadVideoCb (src) { self.loadVideo(src, resolve); }
}
- utils.srcLoader.validateSrc(src, loadImageCb, loadVideoCb);
- function loadImageCb (src) { self.loadImage(src, data, cb); }
- function loadVideoCb (src) { self.loadVideo(src, data, cb); }
+ function sourceLoaded (sourcePromise) {
+ sourceCache[hash] = Promise.resolve(sourcePromise);
+ sourceCache[hash].then(cb);
+ }
},
/**
@@ -81,8 +89,8 @@ module.exports.System = registerSystem('material', {
cube.colorSpace = THREE.SRGBColorSpace;
function loadSide (index) {
- self.loadTexture(srcs[index], {src: srcs[index]}, function (texture) {
- cube.images[index] = texture.image;
+ self.loadTextureSource(srcs[index], function (source) {
+ cube.images[index] = source;
loaded++;
if (loaded === 6) {
cube.needsUpdate = true;
@@ -105,108 +113,54 @@ module.exports.System = registerSystem('material', {
* High-level function for loading image textures (THREE.Texture).
*
* @param {Element|string} src - Texture source.
- * @param {object} data - Texture data.
* @param {function} cb - Callback to pass texture to.
*/
- loadImage: function (src, data, handleImageTextureLoaded) {
- var hash = this.hash(data);
- var textureCache = this.textureCache;
-
- // Texture already being loaded or already loaded. Wait on promise.
- if (textureCache[hash]) {
- textureCache[hash].then(handleImageTextureLoaded);
+ loadImage: function (src, cb) {
+ // Image element provided
+ if (typeof src !== 'string') {
+ cb(new THREE.Source(src));
return;
}
- // Texture not yet being loaded. Start loading it.
- textureCache[hash] = loadImageTexture(src, data);
- textureCache[hash].then(handleImageTextureLoaded);
+ cb(loadImageUrl(src));
},
/**
- * High-level function for loading canvas textures (THREE.Texture).
- *
- * @param {Element|string} src - Texture source.
- * @param {object} data - Texture data.
- * @param {function} cb - Callback to pass texture to.
- */
- loadCanvas: function (src, data, cb) {
- var texture;
- texture = new THREE.CanvasTexture(src);
- setTextureProperties(texture, data);
- cb(texture);
- },
-
- /**
* Load video texture (THREE.VideoTexture).
* Which is just an image texture that RAFs + needsUpdate.
* Note that creating a video texture is synchronous unlike loading an image texture.
* Made asynchronous to be consistent with image textures.
*
* @param {Element|string} src - Texture source.
- * @param {object} data - Texture data.
* @param {function} cb - Callback to pass texture to.
*/
- loadVideo: function (src, data, cb) {
- var hash;
- var texture;
- var textureCache = this.textureCache;
+ loadVideo: function (src, cb) {
var videoEl;
- var videoTextureResult;
-
- function handleVideoTextureLoaded (result) {
- result.texture.needsUpdate = true;
- cb(result.texture, result.videoEl);
- }
// Video element provided.
if (typeof src !== 'string') {
// Check cache before creating texture.
videoEl = src;
- hash = this.hashVideo(data, videoEl);
- if (textureCache[hash]) {
- textureCache[hash].then(handleVideoTextureLoaded);
- return;
- }
- // If not in cache, fix up the attributes then start to create the texture.
+
+ // Fix up the attributes then start to create the texture.
fixVideoAttributes(videoEl);
}
// Only URL provided. Use video element to create texture.
- videoEl = videoEl || createVideoEl(src, data.width, data.height);
-
- // Generated video element already cached. Use that.
- hash = this.hashVideo(data, videoEl);
- if (textureCache[hash]) {
- textureCache[hash].then(handleVideoTextureLoaded);
- return;
- }
+ videoEl = videoEl || createVideoEl(src);
- // Create new video texture.
- texture = new THREE.VideoTexture(videoEl);
- texture.minFilter = THREE.LinearFilter;
- setTextureProperties(texture, data);
-
- // Cache as promise to be consistent with image texture caching.
- videoTextureResult = {texture: texture, videoEl: videoEl};
- textureCache[hash] = Promise.resolve(videoTextureResult);
- handleVideoTextureLoaded(videoTextureResult);
+ cb(new THREE.Source(videoEl));
},
/**
- * Create a hash of the material properties for texture cache key.
+ * Create a hash for a given source.
*/
- hash: function (data) {
- if (data.src.tagName) {
- // Since `data.src` can be an element, parse out the string if necessary for the hash.
- data = utils.extendDeep({}, data);
- data.src = data.src.src;
+ hash: function (src) {
+ if (src.tagName) {
+ // Prefer element's ID or source, otherwise fallback to the element itself
+ return src.id || src.src || src;
}
- return JSON.stringify(data);
- },
-
- hashVideo: function (data, videoEl) {
- return calculateVideoCacheHash(data, videoEl);
+ return src;
},
/**
@@ -226,104 +180,34 @@ module.exports.System = registerSystem('material', {
*/
unregisterMaterial: function (material) {
delete this.materials[material.uuid];
-
- // If any textures on this material are no longer in use, dispose of them.
- var textureCounts = this.textureCounts;
- Object.keys(material)
- .filter(function (propName) {
- return material[propName] && material[propName].isTexture;
- })
- .forEach(function (mapName) {
- textureCounts[material[mapName].uuid]--;
- if (textureCounts[material[mapName].uuid] <= 0) {
- material[mapName].dispose();
- }
- });
- },
-
- /**
- * Track textures used by material components, so that they can be safely
- * disposed when no longer in use. Textures must be registered here, and not
- * through registerMaterial(), because textures may not be attached at the
- * time the material is registered.
- *
- * @param {Event} e
- */
- onMaterialTextureLoaded: function (e) {
- if (!this.textureCounts[e.detail.texture.uuid]) {
- this.textureCounts[e.detail.texture.uuid] = 0;
- }
- this.textureCounts[e.detail.texture.uuid]++;
}
});
/**
- * Calculates consistent hash from a video element using its attributes.
- * If the video element has an ID, use that.
- * Else build a hash that looks like `src:myvideo.mp4;height:200;width:400;`.
- *
- * @param data {object} - Texture data such as repeat.
- * @param videoEl {Element} - Video element.
- * @returns {string}
- */
-function calculateVideoCacheHash (data, videoEl) {
- var i;
- var id = videoEl.getAttribute('id');
- var hash;
- var videoAttributes;
-
- if (id) { return id; }
-
- // Calculate hash using sorted video attributes.
- hash = '';
- videoAttributes = data || {};
- for (i = 0; i < videoEl.attributes.length; i++) {
- videoAttributes[videoEl.attributes[i].name] = videoEl.attributes[i].value;
- }
- Object.keys(videoAttributes).sort().forEach(function (name) {
- hash += name + ':' + videoAttributes[name] + ';';
- });
-
- return hash;
-}
-
-/**
- * Load image texture.
+ * Load image from a given URL.
*
* @private
- * @param {string|object} src - An element or url to an image file.
- * @param {object} data - Data to set texture properties like `repeat`.
+ * @param {string} src - An url to an image file.
* @returns {Promise} Resolves once texture is loaded.
*/
-function loadImageTexture (src, data) {
- return new Promise(doLoadImageTexture);
-
- function doLoadImageTexture (resolve, reject) {
- var isEl = typeof src !== 'string';
-
- function resolveTexture (texture) {
- setTextureProperties(texture, data);
- texture.needsUpdate = true;
- resolve(texture);
- }
-
- // Create texture from an element.
- if (isEl) {
- resolveTexture(new THREE.Texture(src));
- return;
- }
+function loadImageUrl (src) {
+ return new Promise(doLoadImageUrl);
+ function doLoadImageUrl (resolve, reject) {
// Request and load texture from src string. THREE will create underlying element.
- // Use THREE.TextureLoader (src, onLoad, onProgress, onError) to load texture.
- TextureLoader.load(
+ ImageLoader.load(
src,
- resolveTexture,
+ resolveSource,
function () { /* no-op */ },
function (xhr) {
error('`$s` could not be fetched (Error code: %s; Response: %s)', xhr.status,
xhr.statusText);
}
);
+
+ function resolveSource (data) {
+ resolve(new THREE.Source(data));
+ }
}
}
@@ -331,14 +215,10 @@ function loadImageTexture (src, data) {
* Create video element to be used as a texture.
*
* @param {string} src - Url to a video file.
- * @param {number} width - Width of the video.
- * @param {number} height - Height of the video.
* @returns {Element} Video element.
*/
-function createVideoEl (src, width, height) {
+function createVideoEl (src) {
var videoEl = document.createElement('video');
- videoEl.width = width;
- videoEl.height = height;
// Support inline videos for iOS webviews.
videoEl.setAttribute('playsinline', '');
videoEl.setAttribute('webkit-playsinline', '');
@@ -346,7 +226,7 @@ function createVideoEl (src, width, height) {
videoEl.loop = true;
videoEl.crossOrigin = 'anonymous';
videoEl.addEventListener('error', function () {
- warn('`$s` is not a valid video', src);
+ warn('`%s` is not a valid video', src);
}, true);
videoEl.src = src;
return videoEl;
diff --git a/src/systems/renderer.js b/src/systems/renderer.js
index 5b924bc1afd..0d8d669298a 100644
--- a/src/systems/renderer.js
+++ b/src/systems/renderer.js
@@ -78,8 +78,11 @@ module.exports.System = registerSystem('renderer', {
applyColorCorrection: function (texture) {
if (!this.data.colorManagement || !texture) {
return;
- } else if (texture.isTexture) {
+ }
+
+ if (texture.isTexture && texture.colorSpace !== THREE.SRGBColorSpace) {
texture.colorSpace = THREE.SRGBColorSpace;
+ texture.needsUpdate = true;
}
},
diff --git a/src/utils/material.js b/src/utils/material.js
index df7189f8e75..fe93cbd34f9 100644
--- a/src/utils/material.js
+++ b/src/utils/material.js
@@ -1,3 +1,4 @@
+/* global HTMLCanvasElement, HTMLImageElement, HTMLVideoElement */
var THREE = require('../lib/three');
var srcLoader = require('./src-loader');
var debug = require('./debug');
@@ -14,7 +15,7 @@ var COLOR_MAPS = new Set([
* Set texture properties such as repeat and offset.
*
* @param {object} data - With keys like `repeat`.
-*/
+ */
function setTextureProperties (texture, data) {
var offset = data.offset || {x: 0, y: 0};
var repeat = data.repeat || {x: 1, y: 1};
@@ -79,7 +80,7 @@ module.exports.updateMapMaterialFromData = function (materialName, dataName, sha
if (!src) {
// Forget the prior material src.
delete shader.materialSrcs[materialName];
- // Remove the texture.
+ // Remove the texture from the material.
setMap(null);
return;
}
@@ -97,24 +98,55 @@ module.exports.updateMapMaterialFromData = function (materialName, dataName, sha
// If the new material src is already a texture, just use it.
if (src instanceof THREE.Texture) { setMap(src); } else {
- // Load texture for the new material src.
+ // Load texture source for the new material src.
// (And check if we should still use it once available in callback.)
- el.sceneEl.systems.material.loadTexture(src,
- {src: src, repeat: data.repeat, offset: data.offset, npot: data.npot, anisotropy: data.anisotropy},
- checkSetMap);
+ el.sceneEl.systems.material.loadTextureSource(src, updateTexture);
}
- function checkSetMap (texture) {
+ function updateTexture (source) {
// If the source has been changed, don't use loaded texture.
if (shader.materialSrcs[materialName] !== src) { return; }
+
+ var texture = material[materialName];
+
+ // Handle removal or texture type change
+ if (texture && (source === null || !isCompatibleTexture(texture, source))) {
+ texture = null;
+ }
+
+ // Create texture if needed
+ if (!texture && source) {
+ texture = createCompatibleTexture(source);
+ }
+
+ // Update texture source and properties
+ if (texture) {
+ if (texture.source !== source) {
+ texture.source = source;
+ texture.needsUpdate = true;
+ }
+ if (COLOR_MAPS.has(materialName)) {
+ rendererSystem.applyColorCorrection(texture);
+ }
+ setTextureProperties(texture, data);
+ }
+
+ // Set map property on the material
setMap(texture);
}
function setMap (texture) {
- material[materialName] = texture;
- if (texture && COLOR_MAPS.has(materialName)) {
- rendererSystem.applyColorCorrection(texture);
+ // Nothing to do if texture is the same
+ if (material[materialName] === texture) {
+ return;
+ }
+
+ // Dispose old texture if present
+ if (material[materialName]) {
+ material[materialName].dispose();
}
+
+ material[materialName] = texture;
material.needsUpdate = true;
handleTextureEvents(el, texture);
}
@@ -233,12 +265,59 @@ function handleTextureEvents (el, texture) {
// Video events.
if (!texture.image || texture.image.tagName !== 'VIDEO') { return; }
- texture.image.addEventListener('loadeddata', function emitVideoTextureLoadedDataAll () {
+ texture.image.addEventListener('loadeddata', emitVideoTextureLoadedDataAll);
+ texture.image.addEventListener('ended', emitVideoTextureEndedAll);
+ function emitVideoTextureLoadedDataAll () {
el.emit('materialvideoloadeddata', {src: texture.image, texture: texture});
- });
- texture.image.addEventListener('ended', function emitVideoTextureEndedAll () {
+ }
+ function emitVideoTextureEndedAll () {
// Works for non-looping videos only.
el.emit('materialvideoended', {src: texture.image, texture: texture});
+ }
+
+ // Video source can outlive texture, so cleanup event listeners when texture is disposed
+ texture.addEventListener('dispose', function cleanupListeners () {
+ texture.image.removeEventListener('loadeddata', emitVideoTextureLoadedDataAll);
+ texture.image.removeEventListener('ended', emitVideoTextureEndedAll);
});
}
module.exports.handleTextureEvents = handleTextureEvents;
+
+/**
+ * Checks if a given texture type is compatible with a given source.
+ *
+ * @param {THREE.Texture} texture - The texture to check compatibility with
+ * @param {THREE.Source} source - The source to check compatibility with
+ * @returns {boolean} True if the texture is compatible with the source, false otherwise
+ */
+function isCompatibleTexture (texture, source) {
+ if (source.data instanceof HTMLCanvasElement) {
+ return texture.isCanvasTexture;
+ }
+
+ if (source.data instanceof HTMLVideoElement) {
+ // VideoTexture can't have its source changed after initial user
+ return texture.isVideoTexture && texture.source === source;
+ }
+
+ return texture.isTexture && !texture.isCanvasTexture && !texture.isVideoTexture;
+}
+module.exports.isCompatibleTexture = isCompatibleTexture;
+
+function createCompatibleTexture (source) {
+ var texture;
+
+ if (source.data instanceof HTMLCanvasElement) {
+ texture = new THREE.CanvasTexture();
+ } else if (source.data instanceof HTMLVideoElement) {
+ // Pass underlying video to constructor to ensure requestVideoFrameCallback is setup
+ texture = new THREE.VideoTexture(source.data);
+ } else {
+ texture = new THREE.Texture();
+ }
+
+ texture.source = source;
+ texture.needsUpdate = true;
+ return texture;
+}
+module.exports.createCompatibleTexture = createCompatibleTexture;
diff --git a/tests/components/material.test.js b/tests/components/material.test.js
index 99ebc33a3e2..c91addfd22e 100644
--- a/tests/components/material.test.js
+++ b/tests/components/material.test.js
@@ -44,6 +44,32 @@ suite('material', function () {
assert.ok(disposeSpy.called);
});
+ test('disposes material when removing material', function () {
+ var material = el.getObject3D('mesh').material;
+ var disposeSpy = this.sinon.spy(material, 'dispose');
+ el.removeAttribute('material');
+ assert.ok(disposeSpy.called);
+ });
+
+ test('disposes textures when removing material', function () {
+ var material = el.getObject3D('mesh').material;
+ var texture1 = {uuid: 'tex1', isTexture: true, dispose: sinon.spy()};
+ var texture2 = {uuid: 'tex2', isTexture: true, dispose: sinon.spy()};
+ material.map = texture1;
+ material.normalMap = texture2;
+ el.removeAttribute('material');
+ assert.ok(texture1.dispose.called);
+ assert.ok(texture2.dispose.called);
+ });
+
+ test('disposes texture when removing texture', function () {
+ var material = el.getObject3D('mesh').material;
+ var texture1 = {uuid: 'tex1', isTexture: true, dispose: sinon.spy()};
+ material.map = texture1;
+ el.setAttribute('material', 'map', '');
+ assert.ok(texture1.dispose.called);
+ });
+
test('defaults to standard material', function () {
el.removeAttribute('material'); // setup creates a non-default component
el.setAttribute('material', '');
@@ -158,11 +184,11 @@ suite('material', function () {
});
test('invokes XHR if not cached', function (done) {
- var textureLoaderSpy = this.sinon.spy(THREE.TextureLoader.prototype, 'load');
+ var imageLoaderSpy = this.sinon.spy(THREE.ImageLoader.prototype, 'load');
el.addEventListener('materialtextureloaded', function () {
- assert.ok(textureLoaderSpy.called);
+ assert.ok(imageLoaderSpy.called);
assert.ok(IMG_SRC in THREE.Cache.files);
- THREE.TextureLoader.prototype.load.restore();
+ THREE.ImageLoader.prototype.load.restore();
done();
});
el.setAttribute('material', 'src', IMG_SRC);
@@ -252,9 +278,9 @@ suite('material', function () {
el.setAttribute('material', 'side: front');
assert.equal(el.getObject3D('mesh').material.version, oldMaterialVersion);
el.setAttribute('material', 'side: double');
- assert.equal(el.getObject3D('mesh').material.version, oldMaterialVersion + 2);
+ assert.equal(el.getObject3D('mesh').material.version, oldMaterialVersion + 1);
el.setAttribute('material', 'side: front');
- assert.equal(el.getObject3D('mesh').material.version, oldMaterialVersion + 4);
+ assert.equal(el.getObject3D('mesh').material.version, oldMaterialVersion + 2);
});
});
@@ -308,8 +334,8 @@ suite('material', function () {
el.setAttribute('material', 'alphaTest: 0.0');
assert.equal(el.getObject3D('mesh').material.version, oldMaterialVersion);
el.setAttribute('material', 'alphaTest: 1.0');
- // A-Frame sets needsUpdate twice and THREE one more internally when setting alphaTest.
- assert.equal(el.getObject3D('mesh').material.version, oldMaterialVersion + 3);
+ // A-Frame sets needsUpdate once and THREE one more internally when setting alphaTest.
+ assert.equal(el.getObject3D('mesh').material.version, oldMaterialVersion + 2);
});
});
@@ -323,7 +349,7 @@ suite('material', function () {
var oldMaterialVersion = el.getObject3D('mesh').material.version;
el.setAttribute('material', 'vertexColorsEnabled', true);
assert.equal(el.components.material.material.vertexColors, true);
- assert.equal(el.components.material.material.version, oldMaterialVersion + 2);
+ assert.equal(el.components.material.material.version, oldMaterialVersion + 1);
});
});
diff --git a/tests/shaders/phong.test.js b/tests/shaders/phong.test.js
index 69c29b5c053..70db0ae7a63 100644
--- a/tests/shaders/phong.test.js
+++ b/tests/shaders/phong.test.js
@@ -5,7 +5,7 @@ var THREE = require('index').THREE;
suite('phong material', function () {
setup(function (done) {
var el = this.el = entityFactory();
- el.sceneEl.systems.material.clearTextureCache();
+ el.sceneEl.systems.material.clearTextureSourceCache();
el.setAttribute('geometry', '');
el.setAttribute('material', {shader: 'phong'});
if (el.hasLoaded) { done(); }
diff --git a/tests/systems/material.test.js b/tests/systems/material.test.js
index 591f0e946e0..4247ae4c782 100644
--- a/tests/systems/material.test.js
+++ b/tests/systems/material.test.js
@@ -17,7 +17,7 @@ suite('material system', function () {
});
suite('registerMaterial', function () {
- test('registers material to scene', function () {
+ test('registers material to system', function () {
var el = this.el;
var material;
var system;
@@ -45,44 +45,33 @@ suite('material system', function () {
});
suite('unregisterMaterial', function () {
- test('disposes of unused textures', function () {
+ test('unregisters material from system', function () {
var el = this.el;
- var sinon = this.sinon;
var system = el.sceneEl.systems.material;
- var texture1 = {uuid: 'tex1', isTexture: true, dispose: sinon.spy()};
- var texture2 = {uuid: 'tex2', isTexture: true, dispose: sinon.spy()};
- var material1 = {fooMap: texture1, barMap: texture2, dispose: sinon.spy()};
- var material2 = {fooMap: texture1, dispose: sinon.spy()};
+ var material = {uuid: 'material' };
- el.emit('materialtextureloaded', {texture: texture1});
- el.emit('materialtextureloaded', {texture: texture1});
- el.emit('materialtextureloaded', {texture: texture2});
-
- system.unregisterMaterial(material1);
- assert.notOk(texture1.dispose.called);
- assert.ok(texture2.dispose.called);
+ system.registerMaterial(material);
+ assert.equal(system.materials[material.uuid], material);
- system.unregisterMaterial(material2);
- assert.ok(texture1.dispose.called);
- assert.equal(texture2.dispose.callCount, 1);
+ system.unregisterMaterial(material);
+ assert.notOk(system.materials[material.uuid]);
});
});
suite('texture caching', function () {
setup(function () {
- this.system.clearTextureCache();
+ this.system.clearTextureSourceCache();
});
- suite('loadImage', function () {
- test('loads image texture', function (done) {
+ suite('loadTextureSource', function () {
+ test('loads image texture source', function (done) {
var system = this.system;
var src = IMAGE1;
- var data = {src: IMAGE1};
- var hash = system.hash(data);
+ var hash = system.hash(src);
- system.loadImage(src, data, function (texture) {
- system.textureCache[hash].then(function (texture2) {
- assert.equal(texture, texture2);
+ system.loadTextureSource(src, function (source) {
+ system.sourceCache[hash].then(function (source2) {
+ assert.equal(source, source2);
done();
});
});
@@ -90,172 +79,120 @@ suite('material system', function () {
test('loads image given an element', function (done) {
var img = document.createElement('img');
+ img.setAttribute('src', IMAGE1);
+
var system = this.system;
- var data = {src: IMAGE1};
- var hash = system.hash(data);
+ var hash = system.hash(img);
- img.setAttribute('src', IMAGE1);
- system.loadImage(img, data, function (texture) {
- assert.equal(texture.image, img);
- system.textureCache[hash].then(function (texture2) {
- assert.equal(texture, texture2);
+ system.loadTextureSource(img, function (source) {
+ assert.equal(source.data, img);
+ system.sourceCache[hash].then(function (source2) {
+ assert.equal(source, source2);
done();
});
});
});
- test('caches identical image textures', function (done) {
+ test('caches identical image texture sources', function (done) {
var system = this.system;
var src = IMAGE1;
- var data = {src: src};
- var hash = system.hash(data);
+ var hash = system.hash(src);
Promise.all([
- new Promise(function (resolve) { system.loadImage(src, data, resolve); }),
- new Promise(function (resolve) { system.loadImage(src, data, resolve); })
+ new Promise(function (resolve) { system.loadTextureSource(src, resolve); }),
+ new Promise(function (resolve) { system.loadTextureSource(src, resolve); })
]).then(function (results) {
assert.equal(results[0], results[1]);
- assert.ok(system.textureCache[hash]);
- assert.equal(Object.keys(system.textureCache).length, 1);
+ assert.ok(system.sourceCache[hash]);
+ assert.equal(Object.keys(system.sourceCache).length, 1);
done();
});
});
- test('caches different textures for different images', function (done) {
+ test('caches different texture sources for different images', function (done) {
var system = this.system;
var src1 = IMAGE1;
var src2 = IMAGE2;
- var data1 = {src: src1};
- var data2 = {src: src2};
Promise.all([
- new Promise(function (resolve) { system.loadImage(src1, data1, resolve); }),
- new Promise(function (resolve) { system.loadImage(src2, data2, resolve); })
+ new Promise(function (resolve) { system.loadTextureSource(src1, resolve); }),
+ new Promise(function (resolve) { system.loadTextureSource(src2, resolve); })
]).then(function (results) {
assert.notEqual(results[0].uuid, results[1].uuid);
done();
});
});
-
- test('caches different textures for different repeat', function (done) {
- var system = this.system;
- var src = IMAGE1;
- var data1 = {src: src};
- var data2 = {src: src, repeat: {x: 5, y: 5}};
- var hash1 = system.hash(data1);
- var hash2 = system.hash(data2);
-
- Promise.all([
- new Promise(function (resolve) { system.loadImage(src, data1, resolve); }),
- new Promise(function (resolve) { system.loadImage(src, data2, resolve); })
- ]).then(function (results) {
- assert.notEqual(results[0].uuid, results[1].uuid);
- assert.shallowDeepEqual(results[0].repeat, {x: 1, y: 1});
- assert.shallowDeepEqual(results[1].repeat, {x: 5, y: 5});
- assert.equal(Object.keys(system.textureCache).length, 2);
- assert.ok(system.textureCache[hash1]);
- assert.ok(system.textureCache[hash2]);
- done();
- });
- });
});
suite('loadVideo', function () {
test('loads video texture', function (done) {
var system = this.system;
var src = VIDEO1;
- var data = {src: VIDEO1};
- system.loadVideo(src, data, function (texture) {
- var hash = Object.keys(system.textureCache)[0];
- system.textureCache[hash].then(function (result) {
- assert.equal(texture, result.texture);
- assert.equal(texture.image, result.videoEl);
+ system.loadTextureSource(src, function (source) {
+ var hash = Object.keys(system.sourceCache)[0];
+ system.sourceCache[hash].then(function (result) {
+ assert.equal(source, result);
done();
});
});
});
- test('loads image given a