UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

255 lines (254 loc) 8.87 kB
import { ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_RGB8, PIXELFORMAT_RGBA8, TEXTURETYPE_DEFAULT, TEXTURETYPE_RGBM } from "../../platform/graphics/constants.js"; import { Texture } from "../../platform/graphics/texture.js"; import { Asset } from "../asset/asset.js"; import { ResourceHandler } from "./handler.js"; class CubemapHandler extends ResourceHandler { /** * Create a new CubemapHandler instance. * * @param {AppBase} app - The running {@link AppBase}. * @ignore */ constructor(app) { super(app, "cubemap"); this._device = app.graphicsDevice; this._registry = app.assets; this._loader = app.loader; } load(url, callback, asset) { this.loadAssets(asset, callback); } open(url, data, asset) { return asset ? asset.resource : null; } patch(asset, registry) { this.loadAssets(asset, (err, result) => { if (err) { registry.fire("error", asset); registry.fire(`error:${asset.id}`, err, asset); asset.fire("error", asset); } }); } // get the list of dependent asset ids for the cubemap getAssetIds(cubemapAsset) { const result = []; result[0] = cubemapAsset.file; if ((cubemapAsset.loadFaces || !cubemapAsset.file) && cubemapAsset.data && cubemapAsset.data.textures) { for (let i = 0; i < 6; ++i) { result[i + 1] = cubemapAsset.data.textures[i]; } } else { result[1] = result[2] = result[3] = result[4] = result[5] = result[6] = null; } return result; } // test whether two assets ids are the same compareAssetIds(assetIdA, assetIdB) { if (assetIdA && assetIdB) { if (parseInt(assetIdA, 10) === assetIdA || typeof assetIdA === "string") { return assetIdA === assetIdB; } return assetIdA.url === assetIdB.url; } return assetIdA !== null === (assetIdB !== null); } // update the cubemap resources given a newly loaded set of assets with their corresponding ids update(cubemapAsset, assetIds, assets) { const assetData = cubemapAsset.data || {}; const oldAssets = cubemapAsset._handlerState.assets; const oldResources = cubemapAsset._resources; let tex, mip, i; const resources = [null, null, null, null, null, null, null]; const getType = function() { if (assetData.hasOwnProperty("type")) { return assetData.type; } if (assetData.hasOwnProperty("rgbm")) { return assetData.rgbm ? TEXTURETYPE_RGBM : TEXTURETYPE_DEFAULT; } return null; }; if (!cubemapAsset.loaded || assets[0] !== oldAssets[0]) { if (assets[0]) { tex = assets[0].resource; if (tex.cubemap) { for (i = 0; i < 6; ++i) { resources[i + 1] = new Texture(this._device, { name: `${cubemapAsset.name}_prelitCubemap${tex.width >> i}`, cubemap: true, // assume prefiltered data has same encoding as the faces asset type: getType() || tex.type, width: tex.width >> i, height: tex.height >> i, format: tex.format, levels: [tex._levels[i]], addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE, // generate cubemaps on the top level only mipmaps: i === 0 }); } } else { resources[1] = tex; } } } else { resources[1] = oldResources[1] || null; resources[2] = oldResources[2] || null; resources[3] = oldResources[3] || null; resources[4] = oldResources[4] || null; resources[5] = oldResources[5] || null; resources[6] = oldResources[6] || null; } const faceAssets = assets.slice(1); if (!cubemapAsset.loaded || !this.cmpArrays(faceAssets, oldAssets.slice(1))) { if (faceAssets.indexOf(null) === -1) { const faceTextures = faceAssets.map((asset) => { return asset.resource; }); const faceLevels = []; for (mip = 0; mip < faceTextures[0]._levels.length; ++mip) { faceLevels.push(faceTextures.map((faceTexture) => { return faceTexture._levels[mip]; })); } const format = faceTextures[0].format; const faces = new Texture(this._device, { name: `${cubemapAsset.name}_faces`, cubemap: true, type: getType() || faceTextures[0].type, width: faceTextures[0].width, height: faceTextures[0].height, format: format === PIXELFORMAT_RGB8 ? PIXELFORMAT_RGBA8 : format, mipmaps: assetData.mipmaps ?? true, levels: faceLevels, minFilter: assetData.hasOwnProperty("minFilter") ? assetData.minFilter : faceTextures[0].minFilter, magFilter: assetData.hasOwnProperty("magFilter") ? assetData.magFilter : faceTextures[0].magFilter, anisotropy: assetData.hasOwnProperty("anisotropy") ? assetData.anisotropy : 1, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE }); resources[0] = faces; } } else { resources[0] = oldResources[0] || null; } if (!this.cmpArrays(resources, oldResources)) { cubemapAsset.resources = resources; cubemapAsset._handlerState.assetIds = assetIds; cubemapAsset._handlerState.assets = assets; for (i = 0; i < oldResources.length; ++i) { if (oldResources[i] !== null && resources.indexOf(oldResources[i]) === -1) { oldResources[i].destroy(); } } } for (i = 0; i < oldAssets.length; ++i) { if (oldAssets[i] !== null && assets.indexOf(oldAssets[i]) === -1) { oldAssets[i].unload(); } } } cmpArrays(arr1, arr2) { if (arr1.length !== arr2.length) { return false; } for (let i = 0; i < arr1.length; ++i) { if (arr1[i] !== arr2[i]) { return false; } } return true; } // convert string id to int resolveId(value) { const valueInt = parseInt(value, 10); return valueInt === value || valueInt.toString() === value ? valueInt : value; } loadAssets(cubemapAsset, callback) { if (!cubemapAsset.hasOwnProperty("_handlerState")) { cubemapAsset._handlerState = { // the list of requested asset ids in order of [prelit cubemap, 6 faces] assetIds: [null, null, null, null, null, null, null], // the dependent (loaded, active) texture assets assets: [null, null, null, null, null, null, null] }; } const self = this; const assetIds = self.getAssetIds(cubemapAsset); const assets = [null, null, null, null, null, null, null]; const loadedAssetIds = cubemapAsset._handlerState.assetIds; const loadedAssets = cubemapAsset._handlerState.assets; const registry = self._registry; let awaiting = 7; const onLoad = function(index, asset) { assets[index] = asset; awaiting--; if (awaiting === 0) { self.update(cubemapAsset, assetIds, assets); callback(null, cubemapAsset.resources); } }; const onError = function(index, err, asset) { callback(err); }; const processTexAsset = function(index, texAsset2) { if (texAsset2.loaded) { onLoad(index, texAsset2); } else { registry.once(`load:${texAsset2.id}`, onLoad.bind(self, index)); registry.once(`error:${texAsset2.id}`, onError.bind(self, index)); if (!texAsset2.loading) { registry.load(texAsset2); } } }; let texAsset; for (let i = 0; i < 7; ++i) { const assetId = this.resolveId(assetIds[i]); if (!assetId) { onLoad(i, null); } else if (self.compareAssetIds(assetId, loadedAssetIds[i])) { processTexAsset(i, loadedAssets[i]); } else if (parseInt(assetId, 10) === assetId) { texAsset = registry.get(assetId); if (texAsset) { processTexAsset(i, texAsset); } else { setTimeout(((index, assetId_) => { const texAsset2 = registry.get(assetId_); if (texAsset2) { processTexAsset(index, texAsset2); } else { onError(index, `failed to find dependent cubemap asset=${assetId_}`); } }).bind(null, i, assetId)); } } else { const file = typeof assetId === "string" ? { url: assetId, filename: assetId } : assetId; const data = file.url.search(".dds") === -1 ? { type: "rgbp", addressu: "clamp", addressv: "clamp", mipmaps: false } : null; texAsset = new Asset(`${cubemapAsset.name}_part_${i}`, "texture", file, data); registry.add(texAsset); processTexAsset(i, texAsset); } } } } export { CubemapHandler };