From 3a8d90a97cdd3eadd2c9054f46cfa71616235326 Mon Sep 17 00:00:00 2001
From: Svornost <11434-svornost@users.noreply.gitgud.io>
Date: Mon, 8 Mar 2021 23:09:44 -0800
Subject: [PATCH] Use promises to stream textures directly from fetch and
 createImageBitmap instead of using the DOM and document event loop.

---
 src/art/webgl/engine.js | 56 ++++++++++++++++++++++-------------------
 1 file changed, 30 insertions(+), 26 deletions(-)

diff --git a/src/art/webgl/engine.js b/src/art/webgl/engine.js
index d15de10c69f..3eea5de383e 100644
--- a/src/art/webgl/engine.js
+++ b/src/art/webgl/engine.js
@@ -217,43 +217,47 @@ App.Art.Engine = class {
 		}
 	}
 
-	loadTexture(gl, url, engine) {
-		// return dummy texture
+	loadTexture(gl, url) {
+		// return dummy texture right now
 		let texture = gl.createTexture();
 		gl.bindTexture(gl.TEXTURE_2D, texture);
 		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255]));
 
-		// stream real textures
-		let image = new Image();
-		image.onload = function() {
-			gl.bindTexture(gl.TEXTURE_2D, texture);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
-			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
-			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
-
-			// hack to update canvas again after streaming is done
-			engine.loadCount += 1;
-			if (engine.loadCount == engine.sceneData.textures.length) {
-				let containers = document.getElementsByClassName("artContainer");
-				for (let i = 0; i < containers.length; i++) {
-					containers[i].dispatchEvent(new Event("engineLoaded"));
-				}
-			}
-		};
-		image.src = url;
-
-		return texture;
+		// promise that real textures will stream in the future
+		let promise = fetch(url)
+			.then(response => response.blob())
+			.then(blob => createImageBitmap(blob))
+			.then(bitmap => {
+				gl.bindTexture(gl.TEXTURE_2D, texture);
+				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+				gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+				gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
+
+				return texture;
+			});
+
+		return {texture, promise};
 	}
 
 	initTextures() {
 		// load model textures
 		this.modelTextures = [];
-		this.loadCount = 0;
+		let promisedTextures = [];
 		for (let i=0; i < this.sceneData.textures.length; i++) {
-			this.modelTextures[i] = this.loadTexture(this.gl, this.sceneData.textures[i], this);
+			const {texture, promise} = this.loadTexture(this.gl, this.sceneData.textures[i]);
+			this.modelTextures[i] = texture;
+			promisedTextures[i] = promise;
 		}
+		Promise.all(promisedTextures).then(() => {
+			if (App.Art.engineReady) { // re-send loaded event after textures finish streaming
+				let containers = document.getElementsByClassName("artContainer");
+				for (let i = 0; i < containers.length; i++) {
+					containers[i].dispatchEvent(new Event("engineLoaded"));
+				}
+			}
+		});
 	}
 
 	initShaders() {
-- 
GitLab