<!--
This renders two scenes (visually separated by the teal line), each one in its own worker thread.
The second scene loads a big texture, and its rendering will pause. The first scene continues to
render mostly find (if only with a small pause compared to the other scene that loads the texture).
-->

<script id="one" type="x-worker">
  'use strict';

  importScripts('https://unpkg.com/three@0.127.0/build/three.js');

  const state = {
    width: 300,   // canvas default
    height: 150,  // canvas default
  };

  function main(data) {
    const {canvas} = data;
    const renderer = new THREE.WebGLRenderer({canvas});
    renderer.setPixelRatio(2)

    state.width = canvas.width;
    state.height = canvas.height;

    const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 100);
    camera.position.z = 4;

    const scene = new THREE.Scene();

    {
      const light = new THREE.DirectionalLight(0xFFFFFF, 1);
      light.position.set(-1, 2, 4);
      scene.add(light);
    }

    const geometry = new THREE.BoxGeometry(2,2,2);

    function makeInstance(geometry, color, x) {
      const material = new THREE.MeshPhongMaterial({ color });

      const cube = new THREE.Mesh(geometry, material);
      scene.add(cube);

      cube.position.x = x;

      return cube;
    }

    const cube = makeInstance(geometry, 0x44aa88, 0)

    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = state.width;
      const height = state.height;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) renderer.setSize(width, height, false);
      return needResize;
    }

    function render(time) {
      if (resizeRendererToDisplaySize(renderer)) {
        camera.aspect = state.width / state.height;
        camera.updateProjectionMatrix();
      }

      const rot = time * 0.001;
      cube.rotation.x = rot;
      cube.rotation.y = rot;

      renderer.render(scene, camera);

      requestAnimationFrame(render);
    }

    requestAnimationFrame(render);
  }

  function size(data) {
    state.width = data.width;
    state.height = data.height;
  }

  const handlers = {
    main,
    size,
  };

  self.onmessage = function(e) {
    const fn = handlers[e.data.type];
    if (!fn) throw new Error('no handler for type: ' + e.data.type);
    fn(e.data);
  };
</script>

<script id="two" type="x-worker">
  'use strict';

  importScripts('https://unpkg.com/three@0.127.0/build/three.js');

  const state = {
    width: 300,   // canvas default
    height: 150,  // canvas default
  };
  
  const texture = new THREE.Texture
  
  {
    // const url = 'https://assets.codepen.io/191583/andromeda-galaxy.jpg' // 4200x2800
    const url = 'https://assets.codepen.io/191583/tie-fighter-scraps.jpg' // 6400x4000
    
    fetch(url)
      .then(response => response.blob())
      .then(blob => createImageBitmap(blob))
      .then(bitmap => {
        console.log('got texture, ', performance.now())
        texture.image = bitmap
        texture.needsUpdate = true
      })
  }

  function main(data) {
    const {canvas} = data;
    const renderer = new THREE.WebGLRenderer({canvas});
    renderer.setPixelRatio(2)
    const gl = renderer.getContext()
    
    {
      const original = gl.texImage2D
      
      gl.texImage2D = function(...args) {
        let start, finish
        
        console.log('start texture upload, ', start = performance.now())
        const result = original.apply(this, args)
        console.log('done with texture upload, ', finish = performance.now())
        console.log(`time for texture upload: ${finish - start}ms`)
        
        return result
      }
    }

    state.width = canvas.width;
    state.height = canvas.height;

    const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 100);
    camera.position.z = 4;

    const scene = new THREE.Scene();

    {
      const light = new THREE.DirectionalLight(0xFFFFFF, 1);
      light.position.set(-1, 2, 4);
      scene.add(light);
    }

    const geometry = new THREE.BoxGeometry(2,2,2);

    function makeInstance(geometry, color, x) {
      const material = new THREE.MeshPhongMaterial({ color });

      const cube = new THREE.Mesh(geometry, material);
      scene.add(cube);

      cube.position.x = x;

      return cube;
    }

    const cube = makeInstance(geometry, 0x44aa88, 0)
    
    setTimeout(() => {
      cube.material.map = texture
      cube.material.needsUpdate = true
    }, 2000)

    function resizeRendererToDisplaySize(renderer) {
      const canvas = renderer.domElement;
      const width = state.width;
      const height = state.height;
      const needResize = canvas.width !== width || canvas.height !== height;
      if (needResize) renderer.setSize(width, height, false);
      return needResize;
    }

    function render(time) {
      if (resizeRendererToDisplaySize(renderer)) {
        camera.aspect = state.width / state.height;
        camera.updateProjectionMatrix();
      }

      const rot = time * 0.001;
      cube.rotation.x = rot;
      cube.rotation.y = rot;

      renderer.render(scene, camera);

      requestAnimationFrame(render);
    }

    requestAnimationFrame(render);
  }

  function size(data) {
    state.width = data.width;
    state.height = data.height;
  }

  const handlers = {
    main,
    size,
  };

  self.onmessage = function(e) {
    const fn = handlers[e.data.type];
    if (!fn) throw new Error('no handler for type: ' + e.data.type);
    fn(e.data);
  };
</script>

<canvas id="a"></canvas>
<canvas id="b"></canvas>
body {
  margin: 0;
}
canvas {
  outline: 1px solid teal;
  width: 100vw;
  height: 50vh;
  display: block;
}
"use strict";

// scene one
{
  function main() {
    const canvas = document.querySelector("#a");
    const offscreen = canvas.transferControlToOffscreen();
    const worker = new Worker(getWorkerBlob(document.querySelector("#one")));
    worker.postMessage({ type: "main", canvas: offscreen }, [offscreen]);

    function sendSize() {
      worker.postMessage({
        type: "size",
        width: canvas.clientWidth,
        height: canvas.clientHeight
      });
    }

    window.addEventListener("resize", sendSize);
    sendSize();
  }

  main();
}

// scene two
{
  function main() {
    const canvas = document.querySelector("#b");
    const offscreen = canvas.transferControlToOffscreen();
    const worker = new Worker(getWorkerBlob(document.querySelector("#two")));
    worker.postMessage({ type: "main", canvas: offscreen }, [offscreen]);

    function sendSize() {
      worker.postMessage({
        type: "size",
        width: canvas.clientWidth,
        height: canvas.clientHeight
      });
    }

    window.addEventListener("resize", sendSize);
    sendSize();
  }

  main();
}

function getWorkerBlob(scriptElement) {
  let text = scriptElement.text;
  const blob = new Blob([text], { type: "application/javascript" });
  const url = URL.createObjectURL(blob);
  return url;
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.