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

THREE.TextureLoader() loads the same texture multiple times (i.e. 20 times or more) #9824

Closed
3 of 12 tasks
alexprut opened this issue Oct 5, 2016 · 3 comments
Closed
3 of 12 tasks

Comments

@alexprut
Copy link

alexprut commented Oct 5, 2016

Description of the problem

The THREE.TextureLoader() behaves in an unexpected and erroneous way. The load() function of the class tries/loads the same assets multiple times (i.e. 20 times or more).

Below in figure is illustrated this behaviour using the browser console:
bdhg9

The code used to load and use textures follows next:

var Element = function (texture) {
    this.texture = texture;
};
Element.prototype.createShaderMaterial = function (uniforms, vertexShader, fragmentShader) {
    var loader = new THREE.TextureLoader();
    uniforms.texture.value = loader.load(this.texture);

    return new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
        wireframe: true
    });
};

For debugging purposes you can also find a live preview here: https://alexprut.github.io/earth-defender/ and the game code here: https://github.com/alexprut/earth-defender/tree/master/client/js

Three.js version
  • Dev
  • r81
  • r80
Browser
  • All of them
  • Chrome
  • Firefox
  • Internet Explorer
OS
  • All of them
  • Windows
  • Linux
  • Android
  • IOS
Hardware Requirements (graphics card, VR Device, ...)
@calrk
Copy link
Contributor

calrk commented Oct 5, 2016

I believe the loader is working in the intended way. Its loading the meteor textures 200 times because you are asking it to. The loader could be modified to automatically cache and return assets that you ask it to load multiple times, but this may not be wanted in all cases. The browser will be managing the cache so if all the headers on the image file are correct, the browser will return cached versions of the image each time.

In your code:

this.maxMeteorietes = config.maxMeteorietes || 200;

Game.prototype.createMeteorites = function (numMeteorites) {
    var meteorites = new THREE.Object3D();
    for (var i = 0; i < numMeteorites; i++) {
        var meteorite = new Meteorite().create(
            this.createUniforms(),
            this.createVertexShader(),
            this.createFragmentShader()
        );

    ....

}

Meteorite.prototype.create = function (uniforms, vertexShader, fragmentShader) {
    return new THREE.Mesh(
        new THREE.SphereGeometry(5, 5, 5),
//This line is called 200 times, and as such your loader.load() function will be called 200 times.
        this.createShaderMaterial(uniforms, vertexShader, fragmentShader)
    );
};

I would consider adding something like the following:

var cache = [];
var loader = new THREE.TextureLoader();  //don't need a local version of this object

Element.prototype.createShaderMaterial = function (uniforms, vertexShader, fragmentShader) {
    if(cache[this.texture]){
        return cache[this.texture]; //cache[this.texture].clone();
    }
    uniforms.texture.value = loader.load(this.texture);

    var shader = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
        wireframe: true
    });

    cache[this.texture] = shader;

    return shader;
};

So you manage a cache of materials that get loaded from an array rather than regenerated everytime. This will work well if all the asteroids have the same material, or use the .clone() method if you want the materials to be different.

See if that helps out.

@alexprut
Copy link
Author

alexprut commented Oct 6, 2016

@calrk thanks it solves the problem.

I still believe THREE.TextureLoader() behaves in an unexpected way, it should ask you if you want to cache or not the textures, e.g.:

var loader = THREE.TextureLoader();
loader(texture, cache = false);

I think by default the loader should cache the textures for performance reasons (if I'm wrong please let me know why).


Below is a simple solution (Singleton pattern / Module pattern) cache module:

var TextureLoader = (function () {
    var _instance = null;

    var Loader = function () {
        var _loader = new THREE.TextureLoader();
        var _cache = [];

        function _cachePush(elem, val) {
            _cache.push({
                element: elem,
                value: val
            });
        }

        function _cacheSearch(elem) {
            for (var i = 0; i < _cache.length; i++) {
                if (_cache[i].element === elem) {
                    return _cache[i].value;
                }
            }

            return false;
        }

        function load(texture) {
            var match = _cacheSearch(texture);

            if (match) {
                return match;
            }

            var val = _loader.load(texture);
            _cachePush(texture, val);

            return val;
        }

        return {
            load: load
        }
    };

    function getInstance() {
        return (_instance) ? _instance : _instance = Loader();
    }

    return {
        getInstance: getInstance
    }
})();

To use and cache the texture you need to call:

TextureLoader.getInstance().load(texture);

@takahirox
Copy link
Collaborator

takahirox commented Oct 7, 2016

XHRLoader which is called from TextureLoader via ImageLoader uses global object Cache
but default Cache.enabled is false.

If you set THREE.Cache.enabled = true; right after loading three.js
it'd work.

var cached = Cache.get( url );

enabled: false,

#6834

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants