<!--
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
};
let uploadedTexture = false
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`)
uploadedTexture = true
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);
if (uploadedTexture) {
uploadedTexture = false
self.postMessage({type: 'swap-scenes'})
}
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" class="hidden"></canvas>
body {
margin: 0;
}
canvas {
outline: 1px solid teal;
width: 100vw;
height: 100vh;
display: block;
position: absolute;
}
.hidden {
visibility: hidden;
}
"use strict";
let canvas1
// scene one
{
function main() {
canvas1 = document.querySelector("#a");
const offscreen = canvas1.transferControlToOffscreen();
const worker = new Worker(getWorkerBlob(document.querySelector("#one")));
worker.postMessage({ type: "main", canvas: offscreen }, [offscreen]);
function sendSize() {
worker.postMessage({
type: "size",
width: canvas1.clientWidth,
height: canvas1.clientHeight
});
}
window.addEventListener("resize", sendSize);
sendSize();
}
main();
}
// scene two
{
function main() {
const canvas2 = document.querySelector("#b");
const offscreen = canvas2.transferControlToOffscreen();
const worker = new Worker(getWorkerBlob(document.querySelector("#two")));
worker.postMessage({ type: "main", canvas: offscreen }, [offscreen]);
function sendSize() {
worker.postMessage({
type: "size",
width: canvas2.clientWidth,
height: canvas2.clientHeight
});
}
window.addEventListener("resize", sendSize);
sendSize();
worker.addEventListener('message', event => {
if (event.data.type === 'swap-scenes') {
console.log('swap scenes!!!!!')
canvas1.classList.add('hidden')
canvas2.classList.remove('hidden')
}
})
}
main();
}
function getWorkerBlob(scriptElement) {
let text = scriptElement.text;
const blob = new Blob([text], { type: "application/javascript" });
const url = URL.createObjectURL(blob);
return url;
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.