<p dir="auto">I have some spare time.</p>
<p dir="auto"><a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/mrdoob/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/mrdoob">@mrdoob</a> is it something you want in three.js ?</p>
<p dir="auto">On CPU:<br>
<a href="http://cgit.freedesktop.org/mesa/mesa/tree/src/glu/sgi/libnurbs/interface/glinterface.cc?h=8.0" rel="nofollow">http://cgit.freedesktop.org/mesa/mesa/tree/src/glu/sgi/libnurbs/interface/glinterface.cc?h=8.0</a> ( restrictive license )<br>
<a href="https://svn.blender.org/svnroot/bf-blender/trunk/blender/source/blender/blenkernel/intern/curve.c" rel="nofollow">https://svn.blender.org/svnroot/bf-blender/trunk/blender/source/blender/blenkernel/intern/curve.c</a> ( GPL )<br>
<a href="http://www.nar-associates.com/nurbs/c_code.html" rel="nofollow">http://www.nar-associates.com/nurbs/c_code.html</a> ( copyright, non commercial reuse ok )</p>
<p dir="auto">On GPU:<br>
<a href="http://kingkong.me.berkeley.edu/~adarsh/Papers/CAD09.pdf" rel="nofollow">http://kingkong.me.berkeley.edu/~adarsh/Papers/CAD09.pdf</a><br>
10 X faster<br>
but high setup cost</p>
<p dir="auto">This is something I have been struggling with for a while. Three.js doesn't manage your assets very well. It creates duplicates, specifically my problem is with duplication of textures and materials.</p>
<p dir="auto">When you load a texture - you get a brand new <code class="notranslate">Texture</code> object, the <code class="notranslate">TextureLoader</code> under the hood uses <code class="notranslate">Cache</code>, which is a global singleton, meaning that everything that you ever load will be in <code class="notranslate">Cache</code>. There is no eviction, meaning that it's very possible to leak memory here if you're loading dynamic things, like say, if you created a model viewer or an editor of some kind, user loads different models over and over again, and <code class="notranslate">Cache</code> just retains all of that. As a user you have just 2 options here:</p>
<ol dir="auto">
<li>Hunt down specific object and use <code class="notranslate">Cache.remove(key)</code>, or</li>
<li>Use <code class="notranslate">Cache.clear()</code> which is a nuclear option that results in removal of everything in cache</li>
</ol>
<p dir="auto">The <code class="notranslate">Cache</code> is a key-value store, which is not so much of an issue, but it gets used with URL of an asset being loaded as the key - this means that given a URL - you can only ever have 1 representation of the data under that URL. Imagine following scenario:</p>
<p dir="auto">You loaded a texture "happy-cat.png" using <code class="notranslate">TextureLoader</code>, then, down the road, you want to access binary data of the PNG specifically to parse it using some PNG library, suppose you want to access metadata stored in there, such as user name, palette, creation date, creation software etc. So, you use <code class="notranslate">FileLoader</code>. <code class="notranslate">FileLoader</code> uses same <code class="notranslate">Cache</code>, and it will also use the same url ("happy-cat.png") to access the <code class="notranslate">Cache</code>, so it will return you, not a "bytearray" like you asked, but, in fact, a DOM Image. There are more scenarios where this can happen and be an issue. As a minimum, an Asset <code class="notranslate">Cache</code> needs to be able to distinguish typed of Assets and store data from the same source in multiple representations, such as Image and <code class="notranslate">ByteArray</code>.</p>
<p dir="auto">Suppose you are Ms Smarty-Pants and you decided to take all the models in your scene and pack textures for those into a single image, this way you can reduce texture switching to 0! That's crazy good, right? Well... When you load your models, say "Tree", "House" and "Cat" - each of these models will get a new Material, even if that said Material will be identical in your case. Each of these Materials will get its own new Texture, and that means that there will be 3 unique copies of that carefully packed image loaded to the GPU for your scene. Here's the relevant piece of code:<br>
</p><div class="Box Box--condensed my-2">
  <div class="Box-header f6">
    <p class="mb-0 text-bold">
      <a href="https://github.com/mrdoob/three.js/blob/733eb1d58bd70a7e161f3ced21093f186f0e8b9f/src/renderers/webgl/WebGLTextures.js#L294-L324">three.js/src/renderers/webgl/WebGLTextures.js</a>
    </p>
    <p class="mb-0 color-fg-muted">
        Lines 294 to 324
      in
      <a data-pjax="true" class="commit-tease-sha" href="/mrdoob/three.js/commit/733eb1d58bd70a7e161f3ced21093f186f0e8b9f">733eb1d</a>
    </p>
  </div>
  <div itemprop="text" class="Box-body p-0 blob-wrapper blob-wrapper-embedded data">
    <table class="highlight tab-size mb-0 js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">

        <tbody><tr class="border-0">
          <td id="L294" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="294"></td>
          <td id="LC294" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-k">function</span> <span class="pl-en">setTexture2D</span><span class="pl-kos">(</span> <span class="pl-s1">texture</span><span class="pl-kos">,</span> <span class="pl-s1">slot</span> <span class="pl-kos">)</span> <span class="pl-kos">{</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L295" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="295"></td>
          <td id="LC295" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L296" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="296"></td>
          <td id="LC296" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 	<span class="pl-k">var</span> <span class="pl-s1">textureProperties</span> <span class="pl-c1">=</span> <span class="pl-s1">properties</span><span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span> <span class="pl-s1">texture</span> <span class="pl-kos">)</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L297" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="297"></td>
          <td id="LC297" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L298" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="298"></td>
          <td id="LC298" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 	<span class="pl-k">if</span> <span class="pl-kos">(</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">isVideoTexture</span> <span class="pl-kos">)</span> <span class="pl-en">updateVideoTexture</span><span class="pl-kos">(</span> <span class="pl-s1">texture</span> <span class="pl-kos">)</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L299" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="299"></td>
          <td id="LC299" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L300" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="300"></td>
          <td id="LC300" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 	<span class="pl-k">if</span> <span class="pl-kos">(</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">version</span> <span class="pl-c1">&gt;</span> <span class="pl-c1">0</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">textureProperties</span><span class="pl-kos">.</span><span class="pl-c1">__version</span> <span class="pl-c1">!==</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">version</span> <span class="pl-kos">)</span> <span class="pl-kos">{</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L301" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="301"></td>
          <td id="LC301" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L302" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="302"></td>
          <td id="LC302" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 		<span class="pl-k">var</span> <span class="pl-s1">image</span> <span class="pl-c1">=</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">image</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L303" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="303"></td>
          <td id="LC303" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L304" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="304"></td>
          <td id="LC304" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 		<span class="pl-k">if</span> <span class="pl-kos">(</span> <span class="pl-s1">image</span> <span class="pl-c1">===</span> <span class="pl-c1">undefined</span> <span class="pl-kos">)</span> <span class="pl-kos">{</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L305" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="305"></td>
          <td id="LC305" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L306" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="306"></td>
          <td id="LC306" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 			<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">warn</span><span class="pl-kos">(</span> <span class="pl-s">'THREE.WebGLRenderer: Texture marked for update but image is undefined'</span> <span class="pl-kos">)</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L307" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="307"></td>
          <td id="LC307" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L308" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="308"></td>
          <td id="LC308" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 		<span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-k">if</span> <span class="pl-kos">(</span> <span class="pl-s1">image</span><span class="pl-kos">.</span><span class="pl-c1">complete</span> <span class="pl-c1">===</span> <span class="pl-c1">false</span> <span class="pl-kos">)</span> <span class="pl-kos">{</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L309" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="309"></td>
          <td id="LC309" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L310" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="310"></td>
          <td id="LC310" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 			<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">warn</span><span class="pl-kos">(</span> <span class="pl-s">'THREE.WebGLRenderer: Texture marked for update but image is incomplete'</span> <span class="pl-kos">)</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L311" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="311"></td>
          <td id="LC311" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L312" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="312"></td>
          <td id="LC312" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 		<span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L313" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="313"></td>
          <td id="LC313" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L314" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="314"></td>
          <td id="LC314" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 			<span class="pl-en">uploadTexture</span><span class="pl-kos">(</span> <span class="pl-s1">textureProperties</span><span class="pl-kos">,</span> <span class="pl-s1">texture</span><span class="pl-kos">,</span> <span class="pl-s1">slot</span> <span class="pl-kos">)</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L315" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="315"></td>
          <td id="LC315" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 			<span class="pl-k">return</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L316" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="316"></td>
          <td id="LC316" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L317" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="317"></td>
          <td id="LC317" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 		<span class="pl-kos">}</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L318" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="318"></td>
          <td id="LC318" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L319" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="319"></td>
          <td id="LC319" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 	<span class="pl-kos">}</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L320" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="320"></td>
          <td id="LC320" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L321" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="321"></td>
          <td id="LC321" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 	<span class="pl-s1">state</span><span class="pl-kos">.</span><span class="pl-en">activeTexture</span><span class="pl-kos">(</span> <span class="pl-s1">_gl</span><span class="pl-kos">.</span><span class="pl-c1">TEXTURE0</span> <span class="pl-c1">+</span> <span class="pl-s1">slot</span> <span class="pl-kos">)</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L322" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="322"></td>
          <td id="LC322" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> 	<span class="pl-s1">state</span><span class="pl-kos">.</span><span class="pl-en">bindTexture</span><span class="pl-kos">(</span> <span class="pl-s1">_gl</span><span class="pl-kos">.</span><span class="pl-c1">TEXTURE_2D</span><span class="pl-kos">,</span> <span class="pl-s1">textureProperties</span><span class="pl-kos">.</span><span class="pl-c1">__webglTexture</span> <span class="pl-kos">)</span><span class="pl-kos">;</span> </td>
        </tr>

        <tr class="border-0">
          <td id="L323" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="323"></td>
          <td id="LC323" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">  </td>
        </tr>

        <tr class="border-0">
          <td id="L324" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="324"></td>
          <td id="LC324" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-kos">}</span> </td>
        </tr>
    </tbody></table>
  </div>
</div>
<br>
As you can see, "uploadTexture" happens based on texture.version, it doesn't care that this texture is exactly the same as another texture that we already have in memory.<p></p>
<p dir="auto">I am ashamed to admit that I was that Mr Smarty-Pants, and only caught this when doing performance profiling today. I found out that my image is being decoded multiple times and is being uploaded to the GPU multiple times, here's a helpful piece of code to identify such cases that you can put around the "uloadTexture":</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="if (!texture.isDataTexture &amp;&amp; texture.image !== undefined &amp;&amp; texture.image.src) {
   console.time(&quot;Loading Texture &quot; + texture.image.src);
}

uploadTexture(textureProperties, texture, slot);

if (!texture.isDataTexture &amp;&amp; texture.image !== undefined &amp;&amp; texture.image.src) {
   console.timeEnd(&quot;Loading Texture &quot; + texture.image.src);
   console.log(textureProperties, texture, slot);
}"><pre class="notranslate"><span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-c1">!</span><span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">isDataTexture</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">image</span> <span class="pl-c1">!==</span> <span class="pl-c1">undefined</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">image</span><span class="pl-kos">.</span><span class="pl-c1">src</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
   <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">time</span><span class="pl-kos">(</span><span class="pl-s">"Loading Texture "</span> <span class="pl-c1">+</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">image</span><span class="pl-kos">.</span><span class="pl-c1">src</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>

<span class="pl-en">uploadTexture</span><span class="pl-kos">(</span><span class="pl-s1">textureProperties</span><span class="pl-kos">,</span> <span class="pl-s1">texture</span><span class="pl-kos">,</span> <span class="pl-s1">slot</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-c1">!</span><span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">isDataTexture</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">image</span> <span class="pl-c1">!==</span> <span class="pl-c1">undefined</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">image</span><span class="pl-kos">.</span><span class="pl-c1">src</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
   <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">timeEnd</span><span class="pl-kos">(</span><span class="pl-s">"Loading Texture "</span> <span class="pl-c1">+</span> <span class="pl-s1">texture</span><span class="pl-kos">.</span><span class="pl-c1">image</span><span class="pl-kos">.</span><span class="pl-c1">src</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
   <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s1">textureProperties</span><span class="pl-kos">,</span> <span class="pl-s1">texture</span><span class="pl-kos">,</span> <span class="pl-s1">slot</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">I propose that there should be a way to reuse static textures, one way to do that would be to de-couple the <code class="notranslate">Texture</code> Data from <code class="notranslate">Texture</code> Settings and use equality checks (with hashing to make things faster). Similar approach could be applied for <code class="notranslate">Material</code>s too, we'd be able to clean up some of the code that does something functionally similar for materials currently (when compiling shaders).</p>