UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

353 lines (351 loc) 11.5 kB
function BasisWorker() { var BASIS_FORMAT = { cTFETC1: 0, cTFETC2: 1, cTFBC1: 2, cTFBC3: 3, cTFPVRTC1_4_RGB: 8, cTFPVRTC1_4_RGBA: 9, cTFASTC_4x4: 10, cTFATC_RGB: 11, cTFATC_RGBA_INTERPOLATED_ALPHA: 12, cTFRGBA32: 13, cTFRGB565: 14, cTFRGBA4444: 16 }; var opaqueMapping = { astc: BASIS_FORMAT.cTFASTC_4x4, dxt: BASIS_FORMAT.cTFBC1, etc1: BASIS_FORMAT.cTFETC1, etc2: BASIS_FORMAT.cTFETC1, pvr: BASIS_FORMAT.cTFPVRTC1_4_RGB, atc: BASIS_FORMAT.cTFATC_RGB, none: BASIS_FORMAT.cTFRGB565 }; var alphaMapping = { astc: BASIS_FORMAT.cTFASTC_4x4, dxt: BASIS_FORMAT.cTFBC3, etc1: BASIS_FORMAT.cTFRGBA4444, etc2: BASIS_FORMAT.cTFETC2, pvr: BASIS_FORMAT.cTFPVRTC1_4_RGBA, atc: BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA, none: BASIS_FORMAT.cTFRGBA4444 }; var PIXEL_FORMAT = { ETC1: 21, ETC2_RGB: 22, ETC2_RGBA: 23, DXT1: 8, DXT5: 10, PVRTC_4BPP_RGB_1: 26, PVRTC_4BPP_RGBA_1: 27, ASTC_4x4: 28, ATC_RGB: 29, ATC_RGBA: 30, R8_G8_B8_A8: 7, R5_G6_B5: 3, R4_G4_B4_A4: 5 }; var basisToEngineMapping = (basisFormat, deviceDetails)=>{ switch(basisFormat){ case BASIS_FORMAT.cTFETC1: return deviceDetails.formats.etc2 ? PIXEL_FORMAT.ETC2_RGB : PIXEL_FORMAT.ETC1; case BASIS_FORMAT.cTFETC2: return PIXEL_FORMAT.ETC2_RGBA; case BASIS_FORMAT.cTFBC1: return PIXEL_FORMAT.DXT1; case BASIS_FORMAT.cTFBC3: return PIXEL_FORMAT.DXT5; case BASIS_FORMAT.cTFPVRTC1_4_RGB: return PIXEL_FORMAT.PVRTC_4BPP_RGB_1; case BASIS_FORMAT.cTFPVRTC1_4_RGBA: return PIXEL_FORMAT.PVRTC_4BPP_RGBA_1; case BASIS_FORMAT.cTFASTC_4x4: return PIXEL_FORMAT.ASTC_4x4; case BASIS_FORMAT.cTFATC_RGB: return PIXEL_FORMAT.ATC_RGB; case BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA: return PIXEL_FORMAT.ATC_RGBA; case BASIS_FORMAT.cTFRGBA32: return PIXEL_FORMAT.R8_G8_B8_A8; case BASIS_FORMAT.cTFRGB565: return PIXEL_FORMAT.R5_G6_B5; case BASIS_FORMAT.cTFRGBA4444: return PIXEL_FORMAT.R4_G4_B4_A4; } }; var unswizzleGGGR = (data)=>{ var genB = function genB(R, G) { var r = R * (2.0 / 255.0) - 1.0; var g = G * (2.0 / 255.0) - 1.0; var b = Math.sqrt(1.0 - Math.min(1.0, r * r + g * g)); return Math.max(0, Math.min(255, Math.floor((b + 1.0) * 0.5 * 255.0))); }; for(var offset = 0; offset < data.length; offset += 4){ var R = data[offset + 3]; var G = data[offset + 1]; data[offset + 0] = R; data[offset + 2] = genB(R, G); data[offset + 3] = 255; } return data; }; var pack565 = (data)=>{ var result = new Uint16Array(data.length / 4); for(var offset = 0; offset < data.length; offset += 4){ var R = data[offset + 0]; var G = data[offset + 1]; var B = data[offset + 2]; result[offset / 4] = (R & 0xf8) << 8 | (G & 0xfc) << 3 | B >> 3; } return result; }; var isPOT = (width, height)=>{ return (width & width - 1) === 0 && (height & height - 1) === 0; }; var performanceNow = ()=>{ return typeof performance !== 'undefined' ? performance.now() : 0; }; var basis; var rgbPriority; var rgbaPriority; var chooseTargetFormat = (deviceDetails, hasAlpha, isUASTC)=>{ if (isUASTC) { if (deviceDetails.formats.astc) { return 'astc'; } } else { if (hasAlpha) { if (deviceDetails.formats.etc2) { return 'etc2'; } } else { if (deviceDetails.formats.etc2) { return 'etc2'; } if (deviceDetails.formats.etc1) { return 'etc1'; } } } var testInOrder = (priority)=>{ for(var i = 0; i < priority.length; ++i){ var format = priority[i]; if (deviceDetails.formats[format]) { return format; } } return 'none'; }; return testInOrder(hasAlpha ? rgbaPriority : rgbPriority); }; var dimensionsValid = (width, height, format)=>{ switch(format){ case BASIS_FORMAT.cTFETC1: case BASIS_FORMAT.cTFETC2: return true; case BASIS_FORMAT.cTFBC1: case BASIS_FORMAT.cTFBC3: return (width & 0x3) === 0 && (height & 0x3) === 0; case BASIS_FORMAT.cTFPVRTC1_4_RGB: case BASIS_FORMAT.cTFPVRTC1_4_RGBA: return isPOT(width, height); case BASIS_FORMAT.cTFASTC_4x4: return true; case BASIS_FORMAT.cTFATC_RGB: case BASIS_FORMAT.cTFATC_RGBA_INTERPOLATED_ALPHA: return true; } return false; }; var transcodeKTX2 = (url, data, options)=>{ if (!basis.KTX2File) { throw new Error('Basis transcoder module does not include support for KTX2.'); } var funcStart = performanceNow(); var basisFile = new basis.KTX2File(new Uint8Array(data)); var width = basisFile.getWidth(); var height = basisFile.getHeight(); var levels = basisFile.getLevels(); var hasAlpha = !!basisFile.getHasAlpha(); var isUASTC = basisFile.isUASTC && basisFile.isUASTC(); if (!width || !height || !levels) { basisFile.close(); basisFile.delete(); throw new Error("Invalid image dimensions url=" + url + " width=" + width + " height=" + height + " levels=" + levels); } var format = chooseTargetFormat(options.deviceDetails, hasAlpha, isUASTC); var unswizzle = !!options.isGGGR && format === 'pvr'; var basisFormat; if (unswizzle) { basisFormat = BASIS_FORMAT.cTFRGBA32; } else { basisFormat = hasAlpha ? alphaMapping[format] : opaqueMapping[format]; if (!dimensionsValid(width, height, basisFormat)) { basisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565; } } if (!basisFile.startTranscoding()) { basisFile.close(); basisFile.delete(); throw new Error("Failed to start transcoding url=" + url); } var i; var levelData = []; for(var mip = 0; mip < levels; ++mip){ var dstSize = basisFile.getImageTranscodedSizeInBytes(mip, 0, 0, basisFormat); var dst = new Uint8Array(dstSize); if (!basisFile.transcodeImage(dst, mip, 0, 0, basisFormat, 0, -1, -1)) { basisFile.close(); basisFile.delete(); throw new Error("Failed to transcode image url=" + url); } var is16BitFormat = basisFormat === BASIS_FORMAT.cTFRGB565 || basisFormat === BASIS_FORMAT.cTFRGBA4444; levelData.push(is16BitFormat ? new Uint16Array(dst.buffer) : dst); } basisFile.close(); basisFile.delete(); if (unswizzle) { basisFormat = BASIS_FORMAT.cTFRGB565; for(i = 0; i < levelData.length; ++i){ levelData[i] = pack565(unswizzleGGGR(levelData[i])); } } return { format: basisToEngineMapping(basisFormat, options.deviceDetails), width: width, height: height, levels: levelData, cubemap: false, transcodeTime: performanceNow() - funcStart, url: url, unswizzledGGGR: unswizzle }; }; var transcodeBasis = (url, data, options)=>{ var funcStart = performanceNow(); var basisFile = new basis.BasisFile(new Uint8Array(data)); var width = basisFile.getImageWidth(0, 0); var height = basisFile.getImageHeight(0, 0); var images = basisFile.getNumImages(); var levels = basisFile.getNumLevels(0); var hasAlpha = !!basisFile.getHasAlpha(); var isUASTC = basisFile.isUASTC && basisFile.isUASTC(); if (!width || !height || !images || !levels) { basisFile.close(); basisFile.delete(); throw new Error("Invalid image dimensions url=" + url + " width=" + width + " height=" + height + " images=" + images + " levels=" + levels); } var format = chooseTargetFormat(options.deviceDetails, hasAlpha, isUASTC); var unswizzle = !!options.isGGGR && format === 'pvr'; var basisFormat; if (unswizzle) { basisFormat = BASIS_FORMAT.cTFRGBA32; } else { basisFormat = hasAlpha ? alphaMapping[format] : opaqueMapping[format]; if (!dimensionsValid(width, height, basisFormat)) { basisFormat = hasAlpha ? BASIS_FORMAT.cTFRGBA32 : BASIS_FORMAT.cTFRGB565; } } if (!basisFile.startTranscoding()) { basisFile.close(); basisFile.delete(); throw new Error("Failed to start transcoding url=" + url); } var i; var levelData = []; for(var mip = 0; mip < levels; ++mip){ var dstSize = basisFile.getImageTranscodedSizeInBytes(0, mip, basisFormat); var dst = new Uint8Array(dstSize); if (!basisFile.transcodeImage(dst, 0, mip, basisFormat, 0, 0)) { if (mip === levels - 1 && dstSize === levelData[mip - 1].buffer.byteLength) { dst.set(new Uint8Array(levelData[mip - 1].buffer)); console.warn("Failed to transcode last mipmap level, using previous level instead url=" + url); } else { basisFile.close(); basisFile.delete(); throw new Error("Failed to transcode image url=" + url); } } var is16BitFormat = basisFormat === BASIS_FORMAT.cTFRGB565 || basisFormat === BASIS_FORMAT.cTFRGBA4444; levelData.push(is16BitFormat ? new Uint16Array(dst.buffer) : dst); } basisFile.close(); basisFile.delete(); if (unswizzle) { basisFormat = BASIS_FORMAT.cTFRGB565; for(i = 0; i < levelData.length; ++i){ levelData[i] = pack565(unswizzleGGGR(levelData[i])); } } return { format: basisToEngineMapping(basisFormat, options.deviceDetails), width: width, height: height, levels: levelData, cubemap: false, transcodeTime: performanceNow() - funcStart, url: url, unswizzledGGGR: unswizzle }; }; var transcode = (url, data, options)=>{ return options.isKTX2 ? transcodeKTX2(url, data, options) : transcodeBasis(url, data, options); }; var workerTranscode = (url, data, options)=>{ try { var result = transcode(url, data, options); result.levels = result.levels.map((v)=>v.buffer); self.postMessage({ url: url, data: result }, result.levels); } catch (err) { self.postMessage({ url: url, err: err }, null); } }; var workerInit = (config, callback)=>{ var instantiateWasmFunc = (imports, successCallback)=>{ WebAssembly.instantiate(config.module, imports).then((result)=>{ successCallback(result); }).catch((reason)=>{ console.error("instantiate failed + " + reason); }); return {}; }; self.BASIS(config.module ? { instantiateWasm: instantiateWasmFunc } : null).then((instance)=>{ instance.initializeBasis(); basis = instance; rgbPriority = config.rgbPriority; rgbaPriority = config.rgbaPriority; callback(null); }); }; var queue = []; self.onmessage = (message)=>{ var data = message.data; switch(data.type){ case 'init': workerInit(data.config, ()=>{ for(var i = 0; i < queue.length; ++i){ workerTranscode(queue[i].url, queue[i].data, queue[i].options); } queue.length = 0; }); break; case 'transcode': if (basis) { workerTranscode(data.url, data.data, data.options); } else { queue.push(data); } break; } }; } export { BasisWorker };