// javascript code used with Epic Games HTML5 projects // // much of this is for UE4 development purposes. // // to create a custom JS file for your project: // - make a copy of this file - or make one from scratch // - and put it in: "your project folder"/Build/HTML/GameX.js.template // ================================================================================ // ================================================================================ // stubbing in missing/un-supported functions // .../Engine/Source/Runtime/Engine/Private/ActiveSound.cpp // // for velocity-based effects like doppler // ParseParams.Velocity = (ParseParams.Transform.GetTranslation() - LastLocation) / DeltaTime; window.AudioContext = ( window.AudioContext || window.webkitAudioContext || null ); if ( AudioContext ) { var ue4_hacks = {}; // making this obvious... ue4_hacks.ctx = new AudioContext(); ue4_hacks.panner = ue4_hacks.ctx.createPanner(); ue4_hacks.panner.__proto__.setVelocity = ( ue4_hacks.panner.__proto__.setVelocity || function(){} ); } // ================================================================================ // ================================================================================ // project configuration // Minimum WebGL version that the page needs in order to run. UE4 will attempt to use WebGL 2 if available. // Set this to 1 to run with a WebGL 1 fallback if the graphical features required by your UE4 project are // low enough that they do not strictly require WebGL 2. const requiredWebGLVersion = 1; // Add ?webgl1 GET param to explicitly test the WebGL 1 fallback version even if browser does support WebGL 2. const explicitlyUseWebGL1 = (location.search.indexOf('webgl1') != -1); // "Project Settings" -> Platforms -> HTML5 -> Packaging -> "Compress files during shipping packaging" // When hosting UE4 builds live on a production CDN, compression should always be enabled, // since uncompressed files are too huge to be downloaded over the web. // Please view tip in "Project Setting" for more information. const serveCompressedAssets = false; // "Project Settings" -> Project -> Packaging -> "Use Pak File" // For the large .data file, there's two ways to manage compression: either UE4 UnrealPak tool can compress it in engine, or // it can be gzip compressed on disk like other assets. Compressing via UnrealPak has the advantage of storing a smaller data // file to IndexedDB, whereas gzip compressing to disk has the advantage of starting up the page slightly faster. // If true, serve out 'UE4Game.data.gz', if false, serve out 'UE4Game.data'. //const dataFileIsGzipCompressed = false; // ================================================================================ // *** HTML5 emscripten *** var Module = { // state management infoPrinted: false, lastcurrentDownloadedSize: 0, totalDependencies: 0, dataBytesStoredInIndexedDB: 0, // Track how much data is currently stored in IndexedDB. assetDownloadProgress: {}, // Track how many bytes of each needed asset has been downloaded so far. UE4_indexedDBName: 'UE4_assetDatabase_Runner', // this should be an ascii ID string without special characters that is unique to the project that is being packaged UE4_indexedDBVersion: 201902040013, // Bump this number to invalidate existing IDB storages in browsers. }; // ================================================================================ // *** HTML5 UE4 *** Module.arguments = ['../../../Runner/Runner.uproject','-stdout',]; // UE4 Editor or UE4 Frontend with assets "cook on the fly"? if (location.host != "" && (location.search.indexOf('cookonthefly') != -1)) { Module.arguments.push("'-filehostIp=" + location.protocol + "//" + location.host + "'"); } var UE4 = { on_fatal: function() { try { UE4.on_fatal = Module.cwrap('on_fatal', null, ['string', 'string']); } catch(e) { UE4.on_fatal = function() {}; } }, }; // ---------------------------------------- // UE4 error and logging document.addEventListener('error', function(){document.getElementById('clear_indexeddb').style.display = 'inline-block';}, false); function addLog(info, color) { $("#logwindow").append("

" + info + "

"); } Module.print = addLog; Module.printErr = function(text) { console.error(text); }; window.onerror = function(e) { e = e.toString(); if (e.toLowerCase().indexOf('memory') != -1) { e += '
'; if (!heuristic64BitBrowser) e += ' Try running in a 64-bit browser to resolve.'; } showErrorDialog(e); } // ================================================================================ // ================================================================================ // emscripten memory system // Tests if type === 'browser' or type === 'os' is 64-bit or not. function heuristicIs64Bit(type) { function contains(str, substrList) { for(var i in substrList) if (str.indexOf(substrList[i]) != -1) return true; return false; } var ua = (navigator.userAgent + ' ' + navigator.oscpu + ' ' + navigator.platform).toLowerCase(); if (contains(ua, ['wow64'])) return type === 'os'; // 32bit browser on 64bit OS if (contains(ua, ['x86_64', 'amd64', 'ia64', 'win64', 'x64', 'arm64', 'irix64', 'mips64', 'ppc64', 'sparc64'])) return true; if (contains(ua, ['i386', 'i486', 'i586', 'i686', 'x86', 'arm7', 'android', 'mobile', 'win32'])) return false; if (contains(ua, ['intel mac os'])) return true; return false; } // For best stability on 32-bit browsers, allocate asm.js/WebAssembly heap up front before proceeding // to load any other page content. This mitigates the chances that loading up page assets first would // fragment the memory area of the browser process. var pageSize = 64 * 1024; var heuristic64BitBrowser = heuristicIs64Bit('browser'); function alignPageUp(size) { return pageSize * Math.ceil(size / pageSize); } // The application should not be able to allocate more than MAX bytes of memory. If the application // attempts to allocate any more, it will be forbidden. Use this field to defensively impose a // strict limit to an application to keep it from going rogue with its memory usage beyond some // undesired limit. The absolute maximum that is possible is one memory page short of 2GB. var MAX_MEMORY_64BIT = Infinity; var MAX_MEMORY_32BIT = 512*1024*1024; var MAX_MEMORY = Math.min(alignPageUp(heuristic64BitBrowser ? MAX_MEMORY_64BIT : MAX_MEMORY_32BIT), 2048 * 1024 * 1024 - pageSize); // The application needs at least this much memory to run. If the browser can't provide this, // the page startup should be aborted in an out-of-memory exception. var MIN_MEMORY = Math.min(alignPageUp(32 * 1024 * 1024), MAX_MEMORY); // As a hint to the implementation, the application would prefer to reserve this much address // space at startup, and the browser will attempt its best to satisfy this. If this is not // possible, the browser will attempt to allocate anything as close to this IDEAL amount as // possible, but at least MIN bytes. var IDEAL_MEMORY_64BIT = 1024 * 1024 * 1024; var IDEAL_MEMORY_32BIT = 1024 * 1024 * 1024; var IDEAL_MEMORY = Math.min(Math.max(alignPageUp(heuristic64BitBrowser ? IDEAL_MEMORY_64BIT : IDEAL_MEMORY_32BIT), MIN_MEMORY), MAX_MEMORY); // If true, assume the application will have most of its memory allocation pressure inside the // application heap, so reserve address space there up front. If false, assume that the memory // allocation pressure is outside the heap, so avoid reserving memory up front, until needed. var RESERVE_MAX_64BIT = true; var RESERVE_MAX_32BIT = false; var RESERVE_MAX = heuristic64BitBrowser ? RESERVE_MAX_64BIT : RESERVE_MAX_32BIT; function MB(x) { return (x/1024/1024) + 'MB'; } function allocateHeap() { // Try to get as much memory close to IDEAL, but at least MIN. for(var mem = IDEAL_MEMORY; mem >= MIN_MEMORY; mem -= pageSize) { try { if (RESERVE_MAX) Module['wasmMemory'] = new WebAssembly.Memory({ initial: mem / pageSize, maximum: MAX_MEMORY / pageSize }); else Module['wasmMemory'] = new WebAssembly.Memory({ initial: mem / pageSize }); Module['buffer'] = Module['wasmMemory'].buffer; if (Module['buffer'].byteLength != mem) throw 'Out of memory'; break; } catch(e) { /*nop*/ } } if (!Module['buffer'] || !(Module['buffer'].byteLength >= MIN_MEMORY)) { delete Module['buffer']; throw 'Out of memory'; } Module['TOTAL_MEMORY'] = Module['buffer'].byteLength; } allocateHeap(); Module['MAX_MEMORY'] = MAX_MEMORY; console.log('Initial memory size: ' + MB(Module['TOTAL_MEMORY']) + ' (MIN_MEMORY: ' + MB(MIN_MEMORY) + ', IDEAL_MEMORY: ' + MB(IDEAL_MEMORY) + ', MAX_MEMORY: ' + MB(MAX_MEMORY) + ', RESERVE_MAX: ' + RESERVE_MAX + ', heuristic64BitBrowser: ' + heuristic64BitBrowser + ', heuristic64BitOS: ' + heuristicIs64Bit('os') + ')'); // ================================================================================ // WebGL Module['preinitializedWebGLContext'] = null; Module['canvas'] = document.getElementById('canvas'); function getGpuInfo() { var gl = Module['preinitializedWebGLContext']; if (!gl) return '(no GL: ' + Module['webGLErrorReason'] + ')'; var glInfo = ''; var debugInfo = gl.getExtension('WEBGL_debug_renderer_info'); if (debugInfo) glInfo += gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) + ' ' + gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) + '/'; glInfo += gl.getParameter(gl.VENDOR) + ' ' + gl.getParameter(gl.RENDERER); glInfo += ' ' + gl.getParameter(gl.VERSION); glInfo += ', ' + gl.getParameter(gl.SHADING_LANGUAGE_VERSION); if (Module['softwareWebGL']) glInfo += ' (software)'; return glInfo; } function detectWebGL() { var canvas = Module['canvas'] || document.createElement("canvas"); // If you run into problems with WebGL 2, or for quick testing purposes, you can disable UE4 // from using WebGL 2 and revert back to WebGL 1 by setting the following flag to true. var disableWebGL2 = false; if (explicitlyUseWebGL1) { disableWebGL2 = true; console.log('Disabled WebGL 2 as requested by ?webgl1 GET param.'); } var names = ["webgl", "experimental-webgl"]; if (disableWebGL2) { WebGL2RenderingContext = undefined; } else { names = ["webgl2"].concat(names); } function testError(e) { Module['webGLErrorReason'] = e.statusMessage; }; canvas.addEventListener("webglcontextcreationerror", testError, false); try { for(var failIfMajorPerformanceCaveat = 1; failIfMajorPerformanceCaveat >= 0; --failIfMajorPerformanceCaveat) { for(var i in names) { try { var context = canvas.getContext(names[i], {antialias:false,alpha:false,depth:true,stencil:true,failIfMajorPerformanceCaveat:!!failIfMajorPerformanceCaveat}); Module['preinitializedWebGLContext'] = context; Module['softwareWebGL'] = !failIfMajorPerformanceCaveat; if (context && typeof context.getParameter == "function") { if (typeof WebGL2RenderingContext !== 'undefined' && context instanceof WebGL2RenderingContext && names[i] == 'webgl2') { return 2; } else { // We were able to precreate only a WebGL 1 context, remove support for WebGL 2 from the rest of the page execution. WebGL2RenderingContext = undefined; return 1; } } } catch(e) { Module['webGLErrorReason'] = e.toString(); } } } } finally { canvas.removeEventListener("webglcontextcreationerror", testError, false); } return 0; } // ---------------------------------------- // ---------------------------------------- // canvas - scaling // Canvas scaling mode should be set to one of: 1=STRETCH, 2=ASPECT, or 3=FIXED. // This dictates how the canvas size changes when the browser window is resized // by dragging from the corner. var canvasWindowedScaleMode = 2 /*ASPECT*/; // High DPI setting configures whether to match the canvas size 1:1 with // the physical pixels on the screen. // For background, see https://www.khronos.org/webgl/wiki/HandlingHighDPI var canvasWindowedUseHighDpi = true; // Stores the initial size of the canvas in physical pixel units. // If canvasWindowedScaleMode == 3 (FIXED), this size defines the fixed resolution // that the app will render to. // If canvasWindowedScaleMode == 2 (ASPECT), this size defines only the aspect ratio // that the canvas will be constrained to. // If canvasWindowedScaleMode == 1 (STRETCH), these size values are ignored. var canvasAspectRatioWidth = 1366; var canvasAspectRatioHeight = 768; // The resizeCanvas() function recomputes the canvas size on the page as the user changes // the browser window size. function resizeCanvas(aboutToEnterFullscreen) { // Configuration variables, feel free to play around with these to tweak. var minimumCanvasHeightCssPixels = 480; // the visible size of the canvas should always be at least this high (in CSS pixels) var minimumCanvasHeightFractionOfBrowserWindowHeight = 0.65; // and also vertically take up this much % of the total browser client area height. if (aboutToEnterFullscreen && !aboutToEnterFullscreen.type) { // UE4 engine is calling this function right before entering fullscreen? // If you want to perform specific resolution setup here, do so by setting Module['canvas'].width x Module['canvas'].height now, // and configure Module['UE4_fullscreenXXX'] fields above. Most of the time, the defaults are good, so no need to resize here. // Return true here if you want to abort entering fullscreen mode altogether. return; } // The browser called resizeCanvas() to notify that we just entered fullscreen? In that case, we never react, since the strategy is // to always set the canvas size right before entering fullscreen. if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) { return; } var mainArea = document.getElementById('mainarea'); var canvasRect = mainArea.getBoundingClientRect(); // Compute the unconstrained size for the div that encloses the canvas, in CSS pixel units. var cssWidth = canvasRect.right - canvasRect.left; var cssHeight = Math.max(minimumCanvasHeightCssPixels, canvasRect.bottom - canvasRect.top, window.innerHeight * minimumCanvasHeightFractionOfBrowserWindowHeight); if (canvasWindowedScaleMode == 3/*NONE*/) { // In fixed display mode, render to a statically determined WebGL render target size. var newRenderTargetWidth = canvasAspectRatioWidth; var newRenderTargetHeight = canvasAspectRatioHeight; } else { // Convert unconstrained render target size from CSS to physical pixel units. var newRenderTargetWidth = canvasWindowedUseHighDpi ? (cssWidth * window.devicePixelRatio) : cssWidth; var newRenderTargetHeight = canvasWindowedUseHighDpi ? (cssHeight * window.devicePixelRatio) : cssHeight; // Apply aspect ratio constraints, if desired. if (canvasWindowedScaleMode == 2/*ASPECT*/) { if (cssWidth * canvasAspectRatioHeight > canvasAspectRatioWidth * cssHeight) { newRenderTargetWidth = newRenderTargetHeight * canvasAspectRatioWidth / canvasAspectRatioHeight; } else { newRenderTargetHeight = newRenderTargetWidth * canvasAspectRatioHeight / canvasAspectRatioWidth; } } // WebGL render target sizes are always full integer pixels in size, so rounding is critical for CSS size computations below. newRenderTargetWidth = Math.round(newRenderTargetWidth); newRenderTargetHeight = Math.round(newRenderTargetHeight); } // Very subtle but important behavior is that the size of a DOM element on a web page in CSS pixel units can be a fraction, e.g. on // high DPI scaling displays (CSS pixel units are "virtual" pixels). If the CSS size and physical pixel size of the WebGL canvas do // not correspond to each other 1:1 after window.devicePixelRatio scaling has been applied, the result can look blurry. Therefore always // first compute the WebGL render target size first in physical pixels, and convert that back to CSS pixels so that the CSS pixel size // will perfectly align up and the result look clear without scaling applied. cssWidth = canvasWindowedUseHighDpi ? (newRenderTargetWidth / window.devicePixelRatio) : newRenderTargetWidth; cssHeight = canvasWindowedUseHighDpi ? (newRenderTargetHeight / window.devicePixelRatio) : newRenderTargetHeight; Module['canvas'].width = newRenderTargetWidth; Module['canvas'].height = newRenderTargetHeight; Module['canvas'].style.width = cssWidth + 'px'; Module['canvas'].style.height = mainArea.style.height = cssHeight + 'px'; // Tell the engine that the web page has changed the size of the WebGL render target on the canvas (Module['canvas'].width/height). // This will update the GL viewport and propagate the change throughout the engine. // If the CSS style size is changed, this function doesn't need to be called. if (UE_JSlib.UE_CanvasSizeChanged) UE_JSlib.UE_CanvasSizeChanged(); } Module['UE4_resizeCanvas'] = resizeCanvas; // ---------------------------------------- // ---------------------------------------- // canvas - fullscreen // Fullscreen scaling mode behavior (export these to Module object for the engine to read) // This value is one of: // 0=NONE: The same canvas size is kept when entering fullscreen without change. // 1=STRETCH: The canvas is resized to the size of the whole screen, potentially changing aspect ratio. // 2=ASPECT: The canvas is resized to the size of the whole screen, but retaining current aspect ratio. // 3=FIXED: The canvas is centered on screen with a fixed resolution. Module['UE4_fullscreenScaleMode'] = 1;//canvasWindowedScaleMode; // BUG: if using FIXED, fullscreen gets some strange padding on margin... // When entering fullscreen mode, should UE4 engine resize the canvas? // 0=No resizing (do it manually in resizeCanvas()), 1=Resize to standard DPI, 2=Resize to highDPI Module['UE4_fullscreenCanvasResizeMode'] = canvasWindowedUseHighDpi ? 2/*HIDPI*/ : 1/*Standard DPI*/; // Specifies how canvas is scaled to fullscreen, if not rendering in 1:1 pixel perfect mode. // One of 0=Default, 1=Nearest, 2=Bilinear Module['UE4_fullscreenFilteringMode'] = 0; // ================================================================================ // ================================================================================ // IndexDB // NOTE: in a future release of UE4 - this whole section WILL GO AWAY (i.e. handled internally) var enableReadFromIndexedDB = (location.search.indexOf('noidbread') == -1); var enableWriteToIndexedDB = enableReadFromIndexedDB && (location.search.indexOf('noidbwrite') == -1); enableReadFromIndexedDB = false; enableWriteToIndexedDB = false; if (!enableReadFromIndexedDB) showWarningRibbon('Running with IndexedDB access disabled.'); else if (!enableWriteToIndexedDB) showWarningRibbon('Running in read-only IndexedDB access mode.'); function getIDBRequestErrorString(req) { try { return req.error ? ('IndexedDB ' + req.error.name + ': ' + req.error.message) : req.result; } catch(ex) { return null; } } function formatBytes(bytes) { if (bytes >= 1024*1024*1024) return (bytes / (1024*1024*1024)).toFixed(1) + ' GB'; if (bytes >= 1024*1024) return (bytes / (1024*1024)).toFixed(0) + ' MB'; if (bytes >= 1024) return (bytes / 1024).toFixed(1) + ' KB'; return bytes + ' B'; } function reportDataBytesStoredInIndexedDB(deltaBytes) { if (deltaBytes === null) Module['dataBytesStoredInIndexedDB'] = 0; // call with deltaBytes == null to report that DB was cleared. else Module['dataBytesStoredInIndexedDB'] += deltaBytes; document.getElementById('clear_indexeddb').innerText = 'Clear IndexedDB (' + formatBytes(Module['dataBytesStoredInIndexedDB']) + ')'; } function deleteIndexedDBStorage(dbName, onsuccess, onerror, onblocked) { var idb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; if (Module['dbInstance']) Module['dbInstance'].close(); if (!dbName) dbName = Module['UE4_indexedDBName']; var req = idb.deleteDatabase(dbName); req.onsuccess = function() { console.log('Deleted IndexedDB storage ' + dbName + '!'); reportDataBytesStoredInIndexedDB(null); if (onsuccess) onsuccess(); } req.onerror = function(evt) { var errorString = getIDBRequestErrorString(req); console.error('Failed to delete IndexedDB storage ' + dbName + ', ' + errorString); evt.preventDefault(); if (onerror) onerror(errorString); }; req.onblocked = function(evt) { var errorString = getIDBRequestErrorString(req); console.error('Failed to delete IndexedDB storage ' + dbName + ', DB was blocked! ' + errorString); evt.preventDefault(); if (onblocked) onblocked(errorString); } } function storeToIndexedDB(db, key, value) { return new Promise(function(resolve, reject) { if (!enableWriteToIndexedDB) return reject('storeToIndexedDB: IndexedDB writes disabled by "?noidbwrite" option'); function fail(e) { console.error('Failed to store file ' + key + ' to IndexedDB storage! error: ' + e); if (!Module['idberrorShown']) { showWarningRibbon('Failed to store file ' + key + ' to IndexedDB, error: ' + e); Module['idberrorShown'] = true; } return reject(e); } if (!db) return fail('IndexedDB not available!'); if (location.protocol.indexOf('file') != -1) return reject('Loading via file://, skipping caching to IndexedDB'); try { var transaction = db.transaction(['FILES'], 'readwrite'); var packages = transaction.objectStore('FILES'); var putRequest = packages.put(value, "file/" + Module.key + '/' + key); putRequest.onsuccess = function(evt) { if (value.byteLength || value.length) reportDataBytesStoredInIndexedDB(value.size || value.byteLength || value.length); resolve(key); }; putRequest.onerror = function(evt) { var errorString = getIDBRequestErrorString(putRequest) || ('IndexedDB request error: ' + evt); evt.preventDefault(); fail(errorString); }; } catch(e) { fail(e); } }); } function fetchFromIndexedDB(db, key) { return new Promise(function(resolve, reject) { if (!enableReadFromIndexedDB) return reject('fetchFromIndexedDB: IndexedDB reads disabled by "?noidbread" option'); function fail(e) { console.error('Failed to read file ' + key + ' from IndexedDB storage! error:'); console.error(e); if (!Module['idberrorShown']) { showWarningRibbon('Failed to read file ' + key + ' from IndexedDB, error: ' + e); Module['idberrorShown'] = true; } return reject(e); } if (!db) return fail('IndexedDB not available!'); try { var transaction = db.transaction(['FILES'], 'readonly'); var packages = transaction.objectStore('FILES'); var getRequest = packages.get("file/" + Module.key + '/' + key); getRequest.onsuccess = function(evt) { if (evt.target.result) { var len = evt.target.result.size || evt.target.result.byteLength || evt.target.result.length; if (len) reportDataBytesStoredInIndexedDB(len); resolve(evt.target.result); } else { // Succeeded to load, but the load came back with the value of undefined, treat that as an error since we never store undefined in db. reject(); } }; getRequest.onerror = function(evt) { var errorString = getIDBRequestErrorString(getRequest) || ('IndexedDB.get request error: ' + evt); evt.preventDefault(); fail(errorString); }; } catch(e) { fail(e); } }); } function fetchOrDownloadAndStore(db, url, responseType) { return new Promise(function(resolve, reject) { fetchFromIndexedDB(db, url) .then(function(data) { return resolve(data); }) .catch(function(error) { return download(url, responseType) .then(function(data) { // Treat IDB store as separate operation that's not part of the Promise chain. /*return*/ storeToIndexedDB(db, url, data) .then(function() { return resolve(data); }) .catch(function(error) { console.error('Failed to store download to IndexedDB! ' + error); return resolve(data); // succeeded download, but failed to store - ignore failure in that case and just proceed to run by calling the success handler. }) }) .catch(function(error) { return reject(error); }) }); }); } function openIndexedDB(dbName, dbVersion) { return new Promise(function(resolve, reject) { if (!enableReadFromIndexedDB) return reject('openIndexedDB: IndexedDB disabled by "?noidbread" option'); try { var idb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; var openRequest = idb.open(dbName, dbVersion); } catch(e) { return reject(e); } openRequest.onupgradeneeded = function(evt) { var db = evt.target.result; if (db.objectStoreNames.contains('FILES')) db.deleteObjectStore('FILES'); db.createObjectStore('FILES'); }; openRequest.onsuccess = function(evt) { resolve(evt.target.result); }; openRequest.onerror = function(evt) { var errorString = getIDBRequestErrorString(openRequest) || ('IndexedDB request error: ' + evt); evt.preventDefault(); reject(errorString); }; }); } // Module.locateFile() routes asset downloads to either gzip compressed or uncompressed assets. Module.locateFile = function(name) { var serveGzipped = serveCompressedAssets; // When serving from file:// URLs, don't read .gz compressed files, because these files can't be transparently uncompressed. var isFileProtocol = name.indexOf('file://') != -1 || location.protocol.indexOf('file') != -1; if (isFileProtocol) { if (!Module['shownFileProtocolWarning']) { showWarningRibbon('Attempting to load the page via the "file://" protocol. This only works in Firefox, and even there only when not using compression, so attempting to load uncompressed assets. Please host the page on a web server and visit it via a "http://" URL.'); Module['shownFileProtocolWarning'] = true; } serveGzipped = false; } // uncompressing very large gzip files may slow down startup times. // if (!dataFileIsGzipCompressed && name.split('.').slice(-1)[0] == 'data') serveGzipped = false; return serveGzipped ? (name + 'gz') : name; }; // see site/source/docs/api_reference/module.rst for details Module.getPreloadedPackage = function(remotePackageName, remotePackageSize) { return Module['preloadedPackages'] ? Module['preloadedPackages'][remotePackageName] : null; } // ================================================================================ // COMPILER // ---------------------------------------- // wasm Module['instantiateWasm'] = function(info, receiveInstance) { Module['wasmDownloadAction'].then(function(downloadResults) { taskProgress(TASK_COMPILING); var wasmInstantiate = WebAssembly.instantiate(downloadResults.wasmModule || new Uint8Array(downloadResults.wasmBytes), info); return wasmInstantiate.then(function(output) { var instance = output.instance || output; var module = output.module; taskFinished(TASK_COMPILING); Module['wasmInstantiateActionResolve'](instance); receiveInstance(instance); // After a successful instantiation, attempt to save the compiled Wasm Module object to IndexedDB. if (!downloadResults.fromIndexedDB) { storeToIndexedDB(downloadResults.db, 'wasmModule', module).catch(function() { // If the browser did not support storing Wasm Modules to IndexedDB, try to store the Wasm instance instead. return storeToIndexedDB(downloadResults.db, 'wasmBytes', downloadResults.wasmBytes); }); } }); }).catch(function(error) { $ ('#mainarea').empty(); $ ('#mainarea').append(''); }); return {}; } // ---------------------------------------- // shaders function compileShadersFromJson(jsonData) { var shaderPrograms = []; if (jsonData instanceof ArrayBuffer) jsonData = new TextDecoder('utf-8').decode(new DataView(jsonData)); var programsDict = JSON.parse(jsonData); for(var i in programsDict) { shaderPrograms.push(programsDict[i]); } var gl = Module['preinitializedWebGLContext']; Module['precompiledShaders'] = []; Module['precompiledPrograms'] = []; Module['glIDCounter'] = 1; Module['precompiledUniforms'] = [null]; var promise = new Promise(function(resolve, reject) { var nextProgramToBuild = 0; function buildProgram() { if (nextProgramToBuild >= shaderPrograms.length) { taskFinished(TASK_SHADERS); return resolve(); } var p = shaderPrograms[nextProgramToBuild++]; taskProgress(TASK_SHADERS, {current: nextProgramToBuild, total: shaderPrograms.length }); var program = gl.createProgram(); function lineNumberize(str) { str = str.split('\n'); for(var i = 0; i < str.length; ++i) str[i] = (i<9?' ':'') + (i<99?' ':'') + (i+1) + ': ' + str[i]; return str.join('\n'); } var vs = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vs, p.vs); gl.compileShader(vs); var success = gl.getShaderParameter(vs, gl.COMPILE_STATUS); var compileLog = gl.getShaderInfoLog(vs); if (compileLog) compileLog = compileLog.trim(); if (compileLog) console.error('Compiling vertex shader: ' + lineNumberize(p.vs)); if (!success) console.error('Vertex shader compilation failed!'); if (compileLog) console.error('Compilation log: ' + compileLog); if (!success) return reject('Vertex shader compilation failed: ' + compileLog); gl.attachShader(program, vs); Module['precompiledShaders'].push({ vs: p.vs, shader: vs, program: program }); var fs = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fs, p.fs); gl.compileShader(fs); var success = gl.getShaderParameter(fs, gl.COMPILE_STATUS); var compileLog = gl.getShaderInfoLog(fs); if (compileLog) compileLog = compileLog.trim(); if (compileLog) console.error('Compiling fragment shader: ' + lineNumberize(p.fs)); if (!success) console.error('Fragment shader compilation failed!'); if (compileLog) console.error('Compilation log: ' + compileLog); if (!success) return reject('Fragment shader compilation failed: ' + compileLog); gl.attachShader(program, fs); Module['precompiledShaders'].push({ fs: p.fs, shader: fs, program: program }); for(var name in p.attribs) { gl.bindAttribLocation(program, p.attribs[name], name); } gl.linkProgram(program); var success = gl.getProgramParameter(program, gl.LINK_STATUS); var linkLog = gl.getProgramInfoLog(program); if (linkLog) linkLog = linkLog.trim(); if (linkLog) console.error('Linking shader program, vs: \n' + lineNumberize(p.vs) + ', \n fs:\n' + lineNumberize(p.fs)); if (!success) console.error('Shader program linking failed!'); if (linkLog) console.error('Link log: ' + linkLog); if (!success) return reject('Shader linking failed: ' + linkLog); var ptable = { uniforms: {}, maxUniformLength: 0, maxAttributeLength: -1, maxUniformBlockNameLength: -1 }; var GLctx = gl; var utable = ptable.uniforms; var numUniforms = GLctx.getProgramParameter(program, GLctx.ACTIVE_UNIFORMS); for (var i = 0; i < numUniforms; ++i) { var u = GLctx.getActiveUniform(program, i); var name = u.name; ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length + 1); if (name.indexOf("]", name.length - 1) !== -1) { var ls = name.lastIndexOf("["); name = name.slice(0, ls); } var loc = GLctx.getUniformLocation(program, name); var id = Module['glIDCounter']++; utable[name] = [ u.size, id ]; Module['precompiledUniforms'].push(loc); if (Module['precompiledUniforms'].length != Module['glIDCounter']) throw 'uniforms array not in sync! ' + Module['precompiledUniforms'].length + ', ' + Module['glIDCounter']; for (var j = 1; j < u.size; ++j) { var n = name + "[" + j + "]"; loc = GLctx.getUniformLocation(program, n); id = Module['glIDCounter']++; Module['precompiledUniforms'].push(loc); if (Module['precompiledUniforms'].length != Module['glIDCounter']) throw 'uniforms array not in sync! ' + Module['precompiledUniforms'].length + ', ' + Module['glIDCounter']; } } var e = gl.getError(); if (e) { console.error('Precompiling shaders got GL error: ' + e); return reject('Precompiling shaders got GL error: ' + e); } Module['precompiledPrograms'].push({ program: program, programInfos: ptable, vs: p.vs, fs: p.fs }); setTimeout(buildProgram, 0); } setTimeout(buildProgram, 0); }) return promise; } // ================================================================================ // download project files and progress handlers var TASK_DOWNLOADING = 0; var TASK_COMPILING = 1; var TASK_SHADERS = 2; var TASK_MAIN = 3; var loadTasks = [ 'Downloading', 'Compiling WebAssembly', 'Building shaders', 'Launching engine']; function taskProgress(taskId, progress) { var c = document.getElementById('compilingmessage'); if (c) c.style.display = 'block'; else return; var l = document.getElementById('load_' + taskId); if (!l) { var tasks = document.getElementById('loadTasks'); if (!tasks) return; l = document.createElement('div'); l.innerHTML = ' '; tasks.appendChild(l); l = document.getElementById('load_' + taskId); } if (!l.startTime) l.startTime = performance.now(); var text = loadTasks[taskId]; if (progress && progress.total) { text += ': ' + (progress.currentShow || progress.current) + '/' + (progress.totalShow || progress.total) + ' (' + (progress.current * 100 / progress.total).toFixed(0) + '%)'; } else { text += '...'; } l.innerHTML = text; } function taskFinished(taskId, error) { var l = document.getElementById('load_' + taskId); var icon = document.getElementById('icon_' + taskId); if (l && icon) { var totalTime = performance.now() - l.startTime; if (!error) { l.innerHTML = loadTasks[taskId] + ' (' + (totalTime/1000).toFixed(2) + 's)'; icon.className = 'glyphicon glyphicon-ok'; } else { l.innerHTML = loadTasks[taskId] + ': FAILED! ' + error; icon.className = 'glyphicon glyphicon-remove'; showErrorDialog(loadTasks[taskId] + ' failed:
' + error); } } } function reportDownloadProgress(url, downloadedBytes, totalBytes, finished) { Module['assetDownloadProgress'][url] = { current: downloadedBytes, total: totalBytes, finished: finished }; var aggregated = { current: 0, total: 0, finished: true }; for(var i in Module['assetDownloadProgress']) { aggregated.current += Module['assetDownloadProgress'][i].current; aggregated.total += Module['assetDownloadProgress'][i].total; aggregated.finished = aggregated.finished && Module['assetDownloadProgress'][i].finished; } aggregated.currentShow = formatBytes(aggregated.current); aggregated.totalShow = formatBytes(aggregated.total); if (aggregated.finished) taskFinished(TASK_DOWNLOADING); else taskProgress(TASK_DOWNLOADING, aggregated); } function download(url, responseType) { return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = responseType || 'blob'; reportDownloadProgress(url, 0, 1); xhr.onload = function() { if (xhr.status == 0 || (xhr.status >= 200 && xhr.status < 300)) { var len = xhr.response.size || xhr.response.byteLength; reportDownloadProgress(url, len, len, true); resolve(xhr.response); } else { taskFinished(TASK_DOWNLOADING, 'HTTP error ' + (xhr.status || 404) + ' ' + xhr.statusText + ' on file ' + url); reject({ status: xhr.status, statusText: xhr.statusText }); } }; xhr.onprogress = function(p) { if (p.lengthComputable) reportDownloadProgress(url, p.loaded, p.total); }; xhr.onerror = function(e) { var isFileProtocol = url.indexOf('file://') == 0 || location.protocol.indexOf('file') != -1; if (isFileProtocol) taskFinished(TASK_DOWNLOADING, 'HTTP error ' + (xhr.status || 404) + ' ' + xhr.statusText + ' on file ' + url +'
Try using a web server to avoid loading via a "file://" URL.'); // Convert the most common source of errors to a more friendly message format. else taskFinished(TASK_DOWNLOADING, 'HTTP error ' + (xhr.status || 404) + ' ' + xhr.statusText + ' on file ' + url); reject({ status: xhr.status || 404, statusText: xhr.statusText }); }; xhr.onreadystatechange = function() { if (xhr.readyState >= xhr.HEADERS_RECEIVED) { if (url.endsWith('gz') && (xhr.status == 0 || xhr.status == 200)) { if (xhr.getResponseHeader('Content-Encoding') != 'gzip') { // A fallback is to set serveCompressedAssets = false to serve uncompressed assets instead, but that is not really recommended for production use, since gzip compression shrinks // download sizes so dramatically that omitting it for production is not a good idea. taskFinished(TASK_DOWNLOADING, 'Downloaded a compressed file ' + url + ' without the necessary HTTP response header "Content-Encoding: gzip" specified!
Please configure gzip compression on this asset on the web server to serve gzipped assets!'); xhr.onload = xhr.onprogress = xhr.onerror = xhr.onreadystatechange = null; // Abandon tracking events from this XHR further. xhr.abort(); return reject({ status: 406, statusText: 'Not Acceptable' }); } // After enabling Content-Encoding: gzip, make sure that the appropriate MIME type is being used for the asset, i.e. the MIME // type should be that of the uncompressed asset, and not the MIME type of the compression method that was used. if (xhr.getResponseHeader('Content-Type').toLowerCase().indexOf('zip') != -1) { function expectedMimeType(url) { if (url.indexOf('.wasm') != -1) return 'application/wasm'; if (url.indexOf('.js') != -1) return 'application/javascript'; return 'application/octet-stream'; } taskFinished(TASK_DOWNLOADING, 'Downloaded a compressed file ' + url + ' with incorrect HTTP response header "Content-Type: ' + xhr.getResponseHeader('Content-Type') + '"!
Please set the MIME type of the asset to "' + expectedMimeType(url) + '".'); xhr.onload = xhr.onprogress = xhr.onerror = xhr.onreadystatechange = null; // Abandon tracking events from this XHR further. xhr.abort(); return reject({ status: 406, statusText: 'Not Acceptable' }); } } } } xhr.send(null); }); } // ================================================================================ // ================================================================================ // UE4 DEFAULT UX TEMPLATE function showErrorDialog(errorText) { if ( errorText.indexOf('SyntaxError: ') != -1 ) { // this may be due to caching issue -- otherwise, compile time would have caught this errorText = "NOTE: attempting to flush cache and force reload...
Please standby..."; setTimeout(function() { location.reload(true); }, 2000); // 2 seconds } console.error('error: ' + errorText); var existingErrorDialog = document.getElementById('errorDialog'); if (existingErrorDialog) { existingErrorDialog.innerHTML += '
' + errorText; } else { $('#mainarea').empty(); $('#mainarea').append(''); } } function showWarningRibbon(warningText) { var existingWarningDialog = document.getElementById('warningDialog'); if (existingWarningDialog) { existingWarningDialog.innerHTML += '
' + warningText; } else { $('#buttonrow').prepend('
' + warningText + '
'); } } // Given a blob, asynchronously reads the byte contents of that blob to an arraybuffer and returns it as a Promise. function readBlobToArrayBuffer(blob) { return new Promise(function(resolve, reject) { var fileReader = new FileReader(); fileReader.onload = function() { resolve(this.result); } fileReader.onerror = function(e) { reject(e); } fileReader.readAsArrayBuffer(blob); }); } // Asynchronously appends the given script code to DOM. This is to ensure that // browsers parse and compile the JS code parallel to all other execution. function addScriptToDom(scriptCode) { return new Promise(function(resolve, reject) { var script = document.createElement('script'); var blob = (scriptCode instanceof Blob) ? scriptCode : new Blob([scriptCode], { type: 'text/javascript' }); var objectUrl = URL.createObjectURL(blob); script.src = objectUrl; script.onload = function() { script.onload = script.onerror = null; // Remove these onload and onerror handlers, because these capture the inputs to the Promise and the input function, which would leak a lot of memory! URL.revokeObjectURL(objectUrl); // Free up the blob. Note that for debugging purposes, this can be useful to comment out to be able to read the sources in debugger. resolve(); } script.onerror = function(e) { script.onload = script.onerror = null; // Remove these onload and onerror handlers, because these capture the inputs to the Promise and the input function, which would leak a lot of memory! URL.revokeObjectURL(objectUrl); console.error('script failed to add to dom: ' + e); console.error(scriptCode); console.error(e); // The onerror event sends a DOM Level 3 event error object, which does not seem to have any kind of human readable error reason (https://developer.mozilla.org/en-US/docs/Web/Events/error) // There is another error event object at https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent, which would have an error reason. Perhaps that error event might sometimes be fired, // but if not, guess that the error reason was an OOM, since we are dealing with large .js files. reject(e.message || "(out of memory?)"); } document.body.appendChild(script); }); } // ---------------------------------------- // ---------------------------------------- // Startup task which is run after UE4 engine has launched. function postRunEmscripten() { taskFinished(TASK_MAIN); $("#compilingmessage").remove(); // The default Emscripten provided canvas resizing behavior is not needed, // since we are controlling the canvas sizes here, so stub those functions out. Browser.updateCanvasDimensions = function() {}; Browser.setCanvasSize = function() {}; // If you'd like to configure the initial canvas size to render using the resolution // defined in UE4 DefaultEngine.ini [SystemSettings] r.setRes=WidthxHeight, // uncomment the following two lines before calling resizeCanvas() below: // canvasAspectRatioWidth = UE_JSlib.UE_GSystemResolution_ResX(); // canvasAspectRatioHeight = UE_JSlib.UE_GSystemResolution_ResY(); // Configure the size of the canvas and display it. resizeCanvas(); Module['canvas'].style.display = 'block'; // Whenever the browser window size changes, relayout the canvas size on the page. window.addEventListener('resize', resizeCanvas, false); window.addEventListener('orientationchange', resizeCanvas, false); // The following is needed if game is within an iframe - main window already has focus... window.focus(); } Module.postRun = [postRunEmscripten]; // ---------------------------------------- // ---------------------------------------- // MAIN $(document).ready(function() { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Deduce which version to load up. var supportsWasm = (typeof WebAssembly === 'object' && typeof WebAssembly.Memory === 'function'); if (!supportsWasm) { showErrorDialog('Your browser does not support WebAssembly. Please try updating to latest 64-bit browser that supports WebAssembly.
Current user agent: ' + navigator.userAgent); return; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // memory heap if (!Module['buffer'] && allocateHeapUpFront) { showErrorDialog('Failed to allocate ' + MB(MIN_MEMORY) + ' of linear memory for the ' + 'WebAssembly' + ' heap!'); return; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // check for webgl and cache it for later (UE_BrowserWebGLVersion() reads this) Module['WEBGL_VERSION'] = detectWebGL(); console.log(getGpuInfo()); if (!Module['WEBGL_VERSION'] || Module['WEBGL_VERSION'] < requiredWebGLVersion) { showErrorDialog('Your browser does not support WebGL ' + requiredWebGLVersion + '
Error reason: ' + (Module['webGLErrorReason'] || 'Unknown') + '. Try updating your browser and/or graphics card drivers.
Current renderer: ' + getGpuInfo()); return; } function shouldBrowserSupportWebGL2() { var match = window.navigator.userAgent.match(/Firefox\/([0-9]+)\./); if (match) return parseInt(match[1]) >= 51; } if (Module['WEBGL_VERSION'] < 2 && !explicitlyUseWebGL1) { if (shouldBrowserSupportWebGL2()) { showWarningRibbon('Your GPU does not support WebGL 2. This affects graphics performance and quality. Please try updating your graphics driver and/or browser to latest version.
Error reason: ' + (Module['webGLErrorReason'] || 'Unknown') + '
Current renderer: ' + getGpuInfo()); } else { showWarningRibbon('The current browser does not support WebGL 2. This affects graphics performance and quality.
Please try updating your browser (and/or video drivers). NOTE: old hardware might have been blacklisted by this browser -- you may need to use a different browser.
Error reason: ' + (Module['webGLErrorReason'] || 'Unknown') + '
Current renderer: ' + getGpuInfo()); } } // The following WebGL 1.0 extensions are available in core WebGL 2.0 specification, so they are no longer shown in the extensions list. var webGLExtensionsInCoreWebGL2 = ['ANGLE_instanced_arrays','EXT_blend_minmax','EXT_color_buffer_half_float','EXT_frag_depth','EXT_sRGB','EXT_shader_texture_lod','OES_element_index_uint','OES_standard_derivatives','OES_texture_float','OES_texture_half_float','OES_texture_half_float_linear','OES_vertex_array_object','WEBGL_color_buffer_float','WEBGL_depth_texture','WEBGL_draw_buffers']; var supportedWebGLExtensions = Module['preinitializedWebGLContext'].getSupportedExtensions(); if (Module['WEBGL_VERSION'] >= 2) supportedWebGLExtensions = supportedWebGLExtensions.concat(webGLExtensionsInCoreWebGL2); // The following WebGL extensions are required by UE4/this project, and it cannot run without. var requiredWebGLExtensions = []; // TODO: List WebGL extensions here that the demo needs and can't run without. for(var i in requiredWebGLExtensions) { if (supportedWebGLExtensions.indexOf(requiredWebGLExtensions[i]) == -1) { showErrorDialog('Your browser does not support WebGL extension ' + requiredWebGLExtensions[i] + ', which is required to run this page!'); } } // The following WebGL extensions would be preferred to exist for best features/performance, but are not strictly needed and UE4 can fall back if not available. var preferredToHaveWebGLExtensions = [// The following are core in WebGL 2: 'ANGLE_instanced_arrays', // UE4 uses instanced rendering where possible, but can fallback to noninstanced. 'EXT_color_buffer_half_float', 'EXT_sRGB', 'EXT_shader_texture_lod', // textureLod() is needed for correct reflections, without this reflection shaders are missing and render out black. 'OES_standard_derivatives', 'OES_texture_half_float', 'OES_texture_half_float_linear', 'OES_vertex_array_object', 'WEBGL_color_buffer_float', 'WEBGL_depth_texture', 'WEBGL_draw_buffers', // These are still extensions in WebGL 2: 'OES_texture_float', 'WEBGL_compressed_texture_s3tc', 'EXT_texture_filter_anisotropic' ]; var unsupportedWebGLExtensions = []; for(var i in preferredToHaveWebGLExtensions) { if (supportedWebGLExtensions.indexOf(preferredToHaveWebGLExtensions[i]) == -1) { unsupportedWebGLExtensions.push(preferredToHaveWebGLExtensions[i]); } } if (unsupportedWebGLExtensions.length > 1) { showWarningRibbon('Your browser or graphics card does not support the following WebGL extensions: ' + unsupportedWebGLExtensions.join(', ') + '. This can impact UE4 graphics performance and quality.'); } else if (unsupportedWebGLExtensions.length == 1) { showWarningRibbon('Your browser or graphics card does not support the WebGL extension ' + unsupportedWebGLExtensions[0] + '. This can impact UE4 graphics performance and quality.'); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // browser 64bit vs 32bit check if (!heuristicIs64Bit('browser')) { if (heuristicIs64Bit('os')) { showWarningRibbon('It looks like you are running a 32-bit browser on a 64-bit operating system. This can dramatically affect performance and risk running out of memory on large applications. Try updating to a 64-bit browser for an optimized experience.'); } else { showWarningRibbon('It looks like your computer hardware is 32-bit. This can dramatically affect performance.'); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // files to download/cache function withIndexedDB(db) { Module['dbInstance'] = db; // ---------------------------------------- // WASM var mainCompiledCode = fetchFromIndexedDB(db, 'wasmModule').then(function(wasmModule) { return { db: db, wasmModule: wasmModule, fromIndexedDB: true }; }).catch(function() { return fetchFromIndexedDB(db, 'wasmBytes').then(function(wasmBytes) { return { db: db, wasmBytes: wasmBytes, fromIndexedDB: true }; }); }).catch(function() { return download(Module.locateFile('Runner-HTML5-Shipping.wasm'), 'arraybuffer').then(function(wasmBytes) { return { db: db, wasmBytes: wasmBytes, fromIndexedDB: false }; }); }); Module['wasmDownloadAction'] = mainCompiledCode; var compiledCodeInstantiateAction = new Promise(function(resolve, reject) { Module['wasmInstantiateActionResolve'] = resolve; Module['wasmInstantiateActionReject'] = reject; }); // ---------------------------------------- // MAIN JS var mainJsDownload = fetchOrDownloadAndStore(db, Module.locateFile('Runner-HTML5-Shipping.js')).then(function(data) { return addScriptToDom(data).then(function() { addRunDependency('wait-for-compiled-code'); }); }); // ---------------------------------------- // MORE JS var dataJsDownload = fetchOrDownloadAndStore(db, Module.locateFile('Runner-HTML5-Shipping.data.js')); var utilityJsDownload = fetchOrDownloadAndStore(db, Module.locateFile('Utility.js')).then(addScriptToDom); var dataDownload = /* // The following code would download and store the .data file as a Blob, which should be more efficient than loading an ArrayBuffer. However that seems to be buggy, so avoid it for now. fetchOrDownloadAndStore(db, Module.locateFile('Runner-HTML5-Shipping.data')).then(function(dataBlob) { return readBlobToArrayBuffer(dataBlob).then(function(dataArrayBuffer) { Module['preloadedPackages'] = {}; Module['preloadedPackages'][Module.locateFile('Runner-HTML5-Shipping.data')] = dataArrayBuffer; return dataJsDownload.then(addScriptToDom); }) }); */ // Instead as a fallback, download as ArrayBuffer. (TODO: Figure out the bugs with the above, and switch to using that one instead) fetchOrDownloadAndStore(db, Module.locateFile('Runner-HTML5-Shipping.data'), 'arraybuffer').then(function(dataArrayBuffer) { Module['preloadedPackages'] = {}; Module['preloadedPackages'][Module.locateFile('Runner-HTML5-Shipping.data')] = dataArrayBuffer; return dataJsDownload.then(addScriptToDom); }); // ---------------------------------------- // SHADERS const precompileShaders = false; // Currently not enabled. if (precompileShaders) { var compileShaders = fetchOrDownloadAndStore(db, Module.locateFile('shaders.json'), 'arraybuffer') .then(function(json) { return compileShadersFromJson(json) .catch(function(error) { taskFinished(TASK_SHADERS, error + '
Current renderer: ' + getGpuInfo()); throw 'Shader compilation failed'; }); }); } else { var compileShaders = true; // Not precompiling shaders, no-op Promise action. } // ---------------------------------------- // WAIT FOR DOWNLOADS AND COMPILES Promise.all([mainCompiledCode, mainJsDownload, dataJsDownload, utilityJsDownload, dataDownload, compiledCodeInstantiateAction, compileShaders]).then(function() { if (!precompileShaders) { Module['precompiledShaders'] = Module['precompiledPrograms'] = Module['preinitializedWebGLContext'] = Module['glIDCounter'] = Module['precompiledUniforms'] = null; } taskProgress(TASK_MAIN); removeRunDependency('wait-for-compiled-code'); // Now we are ready to call main() }); }; // ---------------------------------------- // GO ! openIndexedDB(Module['UE4_indexedDBName'], Module['UE4_indexedDBVersion'] || 1).then(withIndexedDB).catch(function(e) { console.error('Failed to openIndexedDB, proceeding without reading or storing contents to IndexedDB! Error: '); console.error(e); withIndexedDB(null); }); });